/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright 2020 Joyent, Inc.
 * Copyright (c) 2015, 2016 by Delphix. All rights reserved.
 */

/*
 * svcs - display attributes of service instances
 *
 * We have two output formats and six instance selection mechanisms.  The
 * primary output format is a line of attributes (selected by -o), possibly
 * followed by process description lines (if -p is specified), for each
 * instance selected.  The columns available to display are described by the
 * struct column columns array.  The columns to actually display are kept in
 * the opt_columns array as indicies into the columns array.  The selection
 * mechanisms available for this format are service FMRIs (selects all child
 * instances), instance FMRIs, instance FMRI glob patterns, instances with
 * a certain restarter (-R), dependencies of instances (-d), and dependents of
 * instances (-D).  Since the lines must be sorted (per -sS), we'll just stick
 * each into a data structure and print them in order when we're done.  To
 * avoid listing the same instance twice (when -d and -D aren't given), we'll
 * use a hash table of FMRIs to record that we've listed (added to the tree)
 * an instance.
 *
 * The secondary output format (-l "long") is a paragraph of text for the
 * services or instances selected.  Not needing to be sorted, it's implemented
 * by just calling print_detailed() for each FMRI given.
 */

#include "svcs.h"
#include "notify_params.h"

/* Get the byteorder macros to ease sorting. */
#include <sys/types.h>
#include <netinet/in.h>
#include <inttypes.h>

#include <sys/contract.h>
#include <sys/ctfs.h>
#include <sys/stat.h>

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <libcontract.h>
#include <libcontract_priv.h>
#include <libintl.h>
#include <libscf.h>
#include <libscf_priv.h>
#include <libuutil.h>
#include <libnvpair.h>
#include <libproc.h>
#include <locale.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <time.h>
#include <libzonecfg.h>
#include <zone.h>
#include <sys/ccompile.h>	/* __maybe_unused */

#ifndef TEXT_DOMAIN
#define	TEXT_DOMAIN	"SUNW_OST_OSCMD"
#endif /* TEXT_DOMAIN */

#define	LEGACY_UNKNOWN	"unknown"

/*
 * Per proc(5) when pr_nlwp, pr_nzomb, and pr_lwp.pr_lwpid are all 0,
 * the process is a zombie.
 */
#define	IS_ZOMBIE(_psip) \
	((_psip)->pr_nlwp == 0 && (_psip)->pr_nzomb == 0 && \
	(_psip)->pr_lwp.pr_lwpid == 0)

/*
 * An AVL-storable node for output lines and the keys to sort them by.
 */
struct avl_string {
	uu_avl_node_t	node;
	char		*key;
	char		*str;
};

/*
 * For lists of parsed restarter FMRIs.
 */
struct pfmri_list {
	const char		*scope;
	const char		*service;
	const char		*instance;
	struct pfmri_list	*next;
};


/*
 * Globals
 */
scf_handle_t *h;
static scf_propertygroup_t *g_pg;
static scf_property_t *g_prop;
static scf_value_t *g_val;

static size_t line_sz;			/* Bytes in the header line. */
static size_t sortkey_sz;		/* Bytes in sort keys. */
static uu_avl_pool_t *lines_pool;
static uu_avl_t *lines;			/* Output lines. */
int exit_status;
ssize_t max_scf_name_length;
ssize_t max_scf_value_length;
ssize_t max_scf_fmri_length;
static ssize_t max_scf_type_length;
static time_t now;
static struct pfmri_list *restarters = NULL;
static int first_paragraph = 1;		/* For -l mode. */
static char *common_name_buf;		/* Sized for maximal length value. */
char *locale;				/* Current locale. */
char *g_zonename;			/* zone being operated upon */

/*
 * Pathname storage for path generated from the fmri.
 * Used for reading the ctid and (start) pid files for an inetd service.
 */
static char genfmri_filename[MAXPATHLEN] = "";

/* Options */
static int *opt_columns = NULL;		/* Indices into columns to display. */
static int opt_cnum = 0;
static int opt_processes = 0;		/* Print processes? */
static int *opt_sort = NULL;		/* Indices into columns to sort. */
static int opt_snum = 0;
static int opt_nstate_shown = 0;	/* Will nstate be shown? */
static int opt_verbose = 0;
static char *opt_zone;			/* zone selected, if any */

/* Minimize string constants. */
static const char * const scf_property_state = SCF_PROPERTY_STATE;
static const char * const scf_property_next_state = SCF_PROPERTY_NEXT_STATE;
static const char * const scf_property_contract = SCF_PROPERTY_CONTRACT;


/*
 * Utility functions
 */

/*
 * For unexpected libscf errors.  The ending newline is necessary to keep
 * uu_die() from appending the errno error.
 */
#ifndef NDEBUG
void
do_scfdie(const char *file, int line)
{
	uu_die(gettext("%s:%d: Unexpected libscf error: %s.  Exiting.\n"),
	    file, line, scf_strerror(scf_error()));
}
#else
void
scfdie(void)
{
	uu_die(gettext("Unexpected libscf error: %s.  Exiting.\n"),
	    scf_strerror(scf_error()));
}
#endif

void *
safe_malloc(size_t sz)
{
	void *ptr;

	ptr = malloc(sz);
	if (ptr == NULL)
		uu_die(gettext("Out of memory"));

	return (ptr);
}

char *
safe_strdup(const char *str)
{
	char *cp;

	cp = strdup(str);
	if (cp == NULL)
		uu_die(gettext("Out of memory.\n"));

	return (cp);
}

/*
 * FMRI hashtable.  For uniquifing listings.
 */

struct ht_elem {
	const char	*fmri;
	struct ht_elem	*next;
};

static struct ht_elem	**ht_buckets = NULL;
static uint_t		ht_buckets_num = 0;
static uint_t		ht_num;

static void
ht_free(void)
{
	struct ht_elem *elem, *next;
	int i;

	for (i = 0; i < ht_buckets_num; i++) {
		for (elem = ht_buckets[i]; elem != NULL; elem = next) {
			next = elem->next;
			free((char *)elem->fmri);
			free(elem);
		}
	}

	free(ht_buckets);
	ht_buckets_num = 0;
	ht_buckets = NULL;
}

static void
ht_init(void)
{
	if (ht_buckets != NULL) {
		/*
		 * If we already have a hash table (e.g., because we are
		 * processing multiple zones), destroy it before creating
		 * a new one.
		 */
		struct ht_elem *elem, *next;
		int i;

		for (i = 0; i < ht_buckets_num; i++) {
			for (elem = ht_buckets[i]; elem != NULL; elem = next) {
				next = elem->next;
				free((char *)elem->fmri);
				free(elem);
			}
		}

		free(ht_buckets);
	}

	ht_buckets_num = 8;
	ht_buckets = safe_malloc(sizeof (*ht_buckets) * ht_buckets_num);
	bzero(ht_buckets, sizeof (*ht_buckets) * ht_buckets_num);
	ht_num = 0;
}

static uint_t
ht_hash_fmri(const char *fmri)
{
	uint_t h = 0, g;
	const char *p, *k;

	/* All FMRIs begin with svc:/, so skip that part. */
	assert(strncmp(fmri, "svc:/", sizeof ("svc:/") - 1) == 0);
	k = fmri + sizeof ("svc:/") - 1;

	/*
	 * Generic hash function from uts/common/os/modhash.c.
	 */
	for (p = k; *p != '\0'; ++p) {
		h = (h << 4) + *p;
		if ((g = (h & 0xf0000000)) != 0) {
			h ^= (g >> 24);
			h ^= g;
		}
	}

	return (h);
}

static void
ht_grow()
{
	uint_t new_ht_buckets_num;
	struct ht_elem **new_ht_buckets;
	int i;

	new_ht_buckets_num = ht_buckets_num * 2;
	assert(new_ht_buckets_num > ht_buckets_num);
	new_ht_buckets =
	    safe_malloc(sizeof (*new_ht_buckets) * new_ht_buckets_num);
	bzero(new_ht_buckets, sizeof (*new_ht_buckets) * new_ht_buckets_num);

	for (i = 0; i < ht_buckets_num; ++i) {
		struct ht_elem *elem, *next;

		for (elem = ht_buckets[i]; elem != NULL; elem = next) {
			uint_t h;

			next = elem->next;

			h = ht_hash_fmri(elem->fmri);

			elem->next =
			    new_ht_buckets[h & (new_ht_buckets_num - 1)];
			new_ht_buckets[h & (new_ht_buckets_num - 1)] = elem;
		}
	}

	free(ht_buckets);

	ht_buckets = new_ht_buckets;
	ht_buckets_num = new_ht_buckets_num;
}

/*
 * Add an FMRI to the hash table.  Returns 1 if it was already there,
 * 0 otherwise.
 */
static int
ht_add(const char *fmri)
{
	uint_t h;
	struct ht_elem *elem;

	h = ht_hash_fmri(fmri);

	elem = ht_buckets[h & (ht_buckets_num - 1)];

	for (; elem != NULL; elem = elem->next) {
		if (strcmp(elem->fmri, fmri) == 0)
			return (1);
	}

	/* Grow when average chain length is over 3. */
	if (ht_num > 3 * ht_buckets_num)
		ht_grow();

	++ht_num;

	elem = safe_malloc(sizeof (*elem));
	elem->fmri = strdup(fmri);
	elem->next = ht_buckets[h & (ht_buckets_num - 1)];
	ht_buckets[h & (ht_buckets_num - 1)] = elem;

	return (0);
}



/*
 * Convenience libscf wrapper functions.
 */

/*
 * Get the single value of the named property in the given property group,
 * which must have type ty, and put it in *vp.  If ty is SCF_TYPE_ASTRING, vp
 * is taken to be a char **, and sz is the size of the buffer.  sz is unused
 * otherwise.  Return 0 on success, -1 if the property doesn't exist, has the
 * wrong type, or doesn't have a single value.  If flags has EMPTY_OK, don't
 * complain if the property has no values (but return nonzero).  If flags has
 * MULTI_OK and the property has multiple values, succeed with E2BIG.
 */
int
pg_get_single_val(scf_propertygroup_t *pg, const char *propname, scf_type_t ty,
    void *vp, size_t sz, uint_t flags)
{
	char *buf, root[MAXPATHLEN];
	size_t buf_sz;
	int ret = -1, r;
	boolean_t multi = B_FALSE;

	assert((flags & ~(EMPTY_OK | MULTI_OK)) == 0);

	if (scf_pg_get_property(pg, propname, g_prop) == -1) {
		if (scf_error() != SCF_ERROR_NOT_FOUND)
			scfdie();

		goto out;
	}

	if (scf_property_is_type(g_prop, ty) != SCF_SUCCESS) {
		if (scf_error() == SCF_ERROR_TYPE_MISMATCH)
			goto misconfigured;
		scfdie();
	}

	if (scf_property_get_value(g_prop, g_val) != SCF_SUCCESS) {
		switch (scf_error()) {
		case SCF_ERROR_NOT_FOUND:
			if (flags & EMPTY_OK)
				goto out;
			goto misconfigured;

		case SCF_ERROR_CONSTRAINT_VIOLATED:
			if (flags & MULTI_OK) {
				multi = B_TRUE;
				break;
			}
			goto misconfigured;

		case SCF_ERROR_PERMISSION_DENIED:
		default:
			scfdie();
		}
	}

	switch (ty) {
	case SCF_TYPE_ASTRING:
		r = scf_value_get_astring(g_val, vp, sz);
		if (r == 0 && !(flags & EMPTY_OK)) {
			uu_die(gettext("Unexpected empty string for property "
			    "%s.  Exiting.\n"), propname);
		}
		if (r >= 0)
			r = SCF_SUCCESS;
		break;

	case SCF_TYPE_BOOLEAN:
		r = scf_value_get_boolean(g_val, (uint8_t *)vp);
		break;

	case SCF_TYPE_COUNT:
		r = scf_value_get_count(g_val, (uint64_t *)vp);
		break;

	case SCF_TYPE_INTEGER:
		r = scf_value_get_integer(g_val, (int64_t *)vp);
		break;

	case SCF_TYPE_TIME: {
		int64_t sec;
		int32_t ns;
		r = scf_value_get_time(g_val, &sec, &ns);
		((struct timeval *)vp)->tv_sec = sec;
		((struct timeval *)vp)->tv_usec = ns / 1000;
		break;
	}

	case SCF_TYPE_USTRING:
		r = scf_value_get_ustring(g_val, vp, sz) > 0 ? SCF_SUCCESS : -1;
		break;

	default:
#ifndef NDEBUG
		uu_warn("%s:%d: Unknown type %d.\n", __FILE__, __LINE__, ty);
#endif
		abort();
	}
	if (r != SCF_SUCCESS)
		scfdie();

	ret = multi ? E2BIG : 0;
	goto out;

misconfigured:
	buf_sz = max_scf_fmri_length + 1;
	buf = safe_malloc(buf_sz);
	if (scf_property_to_fmri(g_prop, buf, buf_sz) == -1)
		scfdie();

	uu_warn(gettext("Property \"%s\" is misconfigured.\n"), buf);

	free(buf);

out:
	if (ret != 0 || g_zonename == NULL ||
	    (strcmp(propname, SCF_PROPERTY_LOGFILE) != 0 &&
	    strcmp(propname, SCF_PROPERTY_ALT_LOGFILE) != 0))
		return (ret);

	/*
	 * If we're here, we have a log file and we have specified a zone.
	 * As a convenience, we're going to prepend the zone path to the
	 * name of the log file.
	 */
	root[0] = '\0';
	(void) zone_get_rootpath(g_zonename, root, sizeof (root));
	(void) strlcat(root, vp, sizeof (root));
	(void) snprintf(vp, sz, "%s", root);

	return (ret);
}

static scf_snapshot_t *
get_running_snapshot(scf_instance_t *inst)
{
	scf_snapshot_t *snap;

	snap = scf_snapshot_create(h);
	if (snap == NULL)
		scfdie();

	if (scf_instance_get_snapshot(inst, "running", snap) == 0)
		return (snap);

	if (scf_error() != SCF_ERROR_NOT_FOUND)
		scfdie();

	scf_snapshot_destroy(snap);
	return (NULL);
}

/*
 * As pg_get_single_val(), except look the property group up in an
 * instance.  If "use_running" is set, and the running snapshot exists,
 * do a composed lookup there.  Otherwise, do an (optionally composed)
 * lookup on the current values.  Note that lookups using snapshots are
 * always composed.
 */
int
inst_get_single_val(scf_instance_t *inst, const char *pgname,
    const char *propname, scf_type_t ty, void *vp, size_t sz, uint_t flags,
    int use_running, int composed)
{
	scf_snapshot_t *snap = NULL;
	int r;

	if (use_running)
		snap = get_running_snapshot(inst);
	if (composed || use_running)
		r = scf_instance_get_pg_composed(inst, snap, pgname, g_pg);
	else
		r = scf_instance_get_pg(inst, pgname, g_pg);
	if (snap)
		scf_snapshot_destroy(snap);
	if (r == -1)
		return (-1);

	r = pg_get_single_val(g_pg, propname, ty, vp, sz, flags);

	return (r);
}

static int
instance_enabled(scf_instance_t *inst, boolean_t temp)
{
	uint8_t b;

	if (inst_get_single_val(inst,
	    temp ? SCF_PG_GENERAL_OVR : SCF_PG_GENERAL, SCF_PROPERTY_ENABLED,
	    SCF_TYPE_BOOLEAN, &b, 0, 0, 0, 0) != 0)
		return (-1);

	return (b ? 1 : 0);
}

/*
 * Get a string property from the restarter property group of the given
 * instance.  Return an empty string on normal problems.
 */
static void
get_restarter_string_prop(scf_instance_t *inst, const char *pname,
    char *buf, size_t buf_sz)
{
	if (inst_get_single_val(inst, SCF_PG_RESTARTER, pname,
	    SCF_TYPE_ASTRING, buf, buf_sz, 0, 0, 1) != 0)
		*buf = '\0';
}

static int
get_restarter_time_prop(scf_instance_t *inst, const char *pname,
    struct timeval *tvp, int ok_if_empty)
{
	int r;

	r = inst_get_single_val(inst, SCF_PG_RESTARTER, pname, SCF_TYPE_TIME,
	    tvp, 0, ok_if_empty ? EMPTY_OK : 0, 0, 1);

	return (r == 0 ? 0 : -1);
}

static int
get_restarter_count_prop(scf_instance_t *inst, const char *pname, uint64_t *cp,
    uint_t flags)
{
	return (inst_get_single_val(inst, SCF_PG_RESTARTER, pname,
	    SCF_TYPE_COUNT, cp, 0, flags, 0, 1));
}


/*
 * Generic functions
 */

/*
 * Return an array of pids associated with the given contract id.
 * Returned pids are added to the end of the pidsp array.
 */
static void
ctid_to_pids(uint64_t c, pid_t **pidsp, uint_t *np)
{
	ct_stathdl_t ctst;
	uint_t m;
	int fd;
	int r __maybe_unused, err;
	pid_t *pids;

	fd = contract_open(c, NULL, "status", O_RDONLY);
	if (fd < 0)
		return;

	err = ct_status_read(fd, CTD_ALL, &ctst);
	if (err != 0) {
		uu_warn(gettext("Could not read status of contract "
		    "%ld: %s.\n"), c, strerror(err));
		(void) close(fd);
		return;
	}

	(void) close(fd);

	r = ct_pr_status_get_members(ctst, &pids, &m);
	assert(r == 0);

	if (m == 0) {
		ct_status_free(ctst);
		return;
	}

	*pidsp = realloc(*pidsp, (*np + m) * sizeof (*pidsp));
	if (*pidsp == NULL)
		uu_die(gettext("Out of memory"));

	bcopy(pids, *pidsp + *np, m * sizeof (*pids));
	*np += m;

	ct_status_free(ctst);
}

static int
propvals_to_pids(scf_propertygroup_t *pg, const char *pname, pid_t **pidsp,
    uint_t *np, scf_property_t *prop, scf_value_t *val, scf_iter_t *iter)
{
	scf_type_t ty;
	uint64_t c;
	int r;

	if (scf_pg_get_property(pg, pname, prop) != 0) {
		if (scf_error() != SCF_ERROR_NOT_FOUND)
			scfdie();

		return (ENOENT);
	}

	if (scf_property_type(prop, &ty) != 0)
		scfdie();

	if (ty != SCF_TYPE_COUNT)
		return (EINVAL);

	if (scf_iter_property_values(iter, prop) != 0)
		scfdie();

	for (;;) {
		r = scf_iter_next_value(iter, val);
		if (r == -1)
			scfdie();
		if (r == 0)
			break;

		if (scf_value_get_count(val, &c) != 0)
			scfdie();

		ctid_to_pids(c, pidsp, np);
	}

	return (0);
}

/*
 * Check if instance has general/restarter property that matches
 * given string.  Restarter string must be in canonified form.
 * Returns 0 for success; -1 otherwise.
 */
static int
check_for_restarter(scf_instance_t *inst, const char *restarter)
{
	char	*fmri_buf;
	char	*fmri_buf_canonified = NULL;
	int	ret = -1;

	if (inst == NULL)
		return (-1);

	/* Get restarter */
	fmri_buf = safe_malloc(max_scf_fmri_length + 1);
	if (inst_get_single_val(inst, SCF_PG_GENERAL,
	    SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING, fmri_buf,
	    max_scf_fmri_length + 1, 0, 0, 1) != 0)
		goto out;

	fmri_buf_canonified = safe_malloc(max_scf_fmri_length + 1);
	if (scf_canonify_fmri(fmri_buf, fmri_buf_canonified,
	    (max_scf_fmri_length + 1)) < 0)
		goto out;

	if (strcmp(fmri_buf, restarter) == 0)
		ret = 0;

out:
	free(fmri_buf);
	if (fmri_buf_canonified)
		free(fmri_buf_canonified);
	return (ret);
}

/*
 * Common code that is used by ctids_by_restarter and pids_by_restarter.
 * Checks for a common restarter and if one is available, it generates
 * the appropriate filename using wip->fmri and stores that in the
 * global genfmri_filename.
 *
 * Restarters currently supported are: svc:/network/inetd:default
 * If a restarter specific action is available, then restarter_spec
 * is set to 1.  If a restarter specific action is not available, then
 * restarter_spec is set to 0 and a -1 is returned.
 *
 * Returns:
 * 0 if success: restarter specific action found and filename generated
 * -1 if restarter specific action not found,
 *    if restarter specific action found but an error was encountered
 *    during the generation of the wip->fmri based filename
 */
static int
common_by_restarter(scf_instance_t *inst, const char *fmri,
    int *restarter_specp)
{
	int		ret = -1;
	int		r;

	/* Check for inetd specific restarter */
	if (check_for_restarter(inst, "svc:/network/inetd:default") != 0) {
		*restarter_specp = 0;
		return (ret);
	}

	*restarter_specp = 1;

	/* Get the ctid filename associated with this instance */
	r = gen_filenms_from_fmri(fmri, "ctid", genfmri_filename, NULL);

	switch (r) {
	case 0:
		break;

	case -1:
		/*
		 * Unable to get filename from fmri.  Print warning
		 * and return failure with no ctids.
		 */
		uu_warn(gettext("Unable to read contract ids for %s -- "
		    "FMRI is too long\n"), fmri);
		return (ret);

	case -2:
		/*
		 * The directory didn't exist, so no contracts.
		 * Return failure with no ctids.
		 */
		return (ret);

	default:
		uu_warn(gettext("%s:%d: gen_filenms_from_fmri() failed with "
		    "unknown error %d\n"), __FILE__, __LINE__, r);
		abort();
	}

	return (0);

}

/*
 * Get or print a contract id using a restarter specific action.
 *
 * If the print_flag is not set, this routine gets the single contract
 * id associated with this instance.
 * If the print flag is set, then print each contract id found.
 *
 * Returns:
 * 0 if success: restarter specific action found and used with no error
 * -1 if restarter specific action not found
 * -1 if restarter specific action found, but there was a failure
 * -1 if print flag is not set and no contract id is found or multiple
 *    contract ids were found
 * E2BIG if print flag is not set, MULTI_OK bit in flag is set and multiple
 *    contract ids were found
 */
static int
ctids_by_restarter(scf_walkinfo_t *wip, uint64_t *cp, int print_flag,
    uint_t flags, int *restarter_specp, void (*callback_header)(),
    void (*callback_ctid)(uint64_t))
{
	FILE		*fp;
	int		ret = -1;
	int		fscanf_ret;
	uint64_t	cp2;
	int		rest_ret;

	/* Check if callbacks are needed and were passed in */
	if (print_flag) {
		if ((callback_header == NULL) || (callback_ctid == NULL))
			return (ret);
	}

	/* Check for restarter specific action and generation of filename */
	rest_ret = common_by_restarter(wip->inst, wip->fmri, restarter_specp);
	if (rest_ret != 0)
		return (rest_ret);

	/*
	 * If fopen fails, then ctid file hasn't been created yet.
	 * If print_flag is set, this is ok; otherwise fail.
	 */
	if ((fp = fopen(genfmri_filename, "r")) == NULL) {
		if (print_flag)
			return (0);
		goto out;
	}

	if (print_flag) {
		/*
		 * Print all contract ids that are found.
		 * First callback to print ctid header.
		 */
		callback_header();

		/* fscanf may not set errno, so be sure to clear it first */
		errno = 0;
		while ((fscanf_ret = fscanf(fp, "%llu", cp)) == 1) {
			/* Callback to print contract id */
			callback_ctid(*cp);
			errno = 0;
		}
		/* EOF is not a failure when no errno. */
		if ((fscanf_ret != EOF) || (errno != 0)) {
			uu_die(gettext("Unable to read ctid file for %s"),
			    wip->fmri);
		}
		(void) putchar('\n');
		ret = 0;
	} else {
		/* Must find 1 ctid or fail */
		if (fscanf(fp, "%llu", cp) == 1) {
			/* If 2nd ctid found - fail */
			if (fscanf(fp, "%llu", &cp2) == 1) {
				if (flags & MULTI_OK)
					ret = E2BIG;
			} else {
				/* Success - found only 1 ctid */
				ret = 0;
			}
		}
	}
	(void) fclose(fp);

out:
	return (ret);
}

/*
 * Get the process ids associated with an instance using a restarter
 * specific action.
 *
 * Returns:
 *	0 if success: restarter specific action found and used with no error
 *	-1 restarter specific action not found or if failure
 */
static int
pids_by_restarter(scf_instance_t *inst, const char *fmri,
    pid_t **pids, uint_t *np, int *restarter_specp)
{
	uint64_t	c;
	FILE		*fp;
	int		fscanf_ret;
	int		rest_ret;

	/* Check for restarter specific action and generation of filename */
	rest_ret = common_by_restarter(inst, fmri, restarter_specp);
	if (rest_ret != 0)
		return (rest_ret);

	/*
	 * If fopen fails with ENOENT then the ctid file hasn't been
	 * created yet so return success.
	 * For all other errors - fail with uu_die.
	 */
	if ((fp = fopen(genfmri_filename, "r")) == NULL) {
		if (errno == ENOENT)
			return (0);
		uu_die(gettext("Unable to open ctid file for %s"), fmri);
	}

	/* fscanf may not set errno, so be sure to clear it first */
	errno = 0;
	while ((fscanf_ret = fscanf(fp, "%llu", &c)) == 1) {
		if (c == 0) {
			(void) fclose(fp);
			uu_die(gettext("ctid file for %s has corrupt data"),
			    fmri);
		}
		ctid_to_pids(c, pids, np);
		errno = 0;
	}
	/* EOF is not a failure when no errno. */
	if ((fscanf_ret != EOF) || (errno != 0)) {
		uu_die(gettext("Unable to read ctid file for %s"), fmri);
	}

	(void) fclose(fp);
	return (0);
}

static int
instance_processes(scf_instance_t *inst, const char *fmri,
    pid_t **pids, uint_t *np)
{
	scf_iter_t *iter;
	int ret;
	int restarter_spec;

	/* Use the restarter specific get pids routine, if available. */
	ret = pids_by_restarter(inst, fmri, pids, np, &restarter_spec);
	if (restarter_spec == 1)
		return (ret);

	if ((iter = scf_iter_create(h)) == NULL)
		scfdie();

	if (scf_instance_get_pg(inst, SCF_PG_RESTARTER, g_pg) == 0) {
		*pids = NULL;
		*np = 0;

		(void) propvals_to_pids(g_pg, scf_property_contract, pids, np,
		    g_prop, g_val, iter);

		(void) propvals_to_pids(g_pg, SCF_PROPERTY_TRANSIENT_CONTRACT,
		    pids, np, g_prop, g_val, iter);

		ret = 0;
	} else {
		if (scf_error() != SCF_ERROR_NOT_FOUND)
			scfdie();

		ret = -1;
	}

	scf_iter_destroy(iter);

	return (ret);
}

/*
 * Column sprint and sortkey functions
 */

struct column {
	const char *name;
	int width;

	/*
	 * This function should write the value for the column into buf, and
	 * grow or allocate buf accordingly.  It should always write at least
	 * width bytes, blanking unused bytes with spaces.  If the field is
	 * greater than the column width we allow it to overlap other columns.
	 * In particular, it shouldn't write any null bytes.  (Though an extra
	 * null byte past the end is currently tolerated.)  If the property
	 * group is non-NULL, then we are dealing with a legacy service.
	 */
	void (*sprint)(char **, scf_walkinfo_t *);

	int sortkey_width;

	/*
	 * This function should write sortkey_width bytes into buf which will
	 * cause memcmp() to sort it properly.  (Unlike sprint() above,
	 * however, an extra null byte may overrun the buffer.)  The second
	 * argument controls whether the results are sorted in forward or
	 * reverse order.
	 */
	void (*get_sortkey)(char *, int, scf_walkinfo_t *);
};

static void
reverse_bytes(char *buf, size_t len)
{
	int i;

	for (i = 0; i < len; ++i)
		buf[i] = ~buf[i];
}

/* CTID */
#define	CTID_COLUMN_WIDTH		6
#define	CTID_COLUMN_BUFSIZE		20	/* max ctid_t + space + \0 */

static void
sprint_ctid(char **buf, scf_walkinfo_t *wip)
{
	int r;
	uint64_t c;
	size_t newsize = (*buf ? strlen(*buf) : 0) + CTID_COLUMN_BUFSIZE;
	char *newbuf = safe_malloc(newsize);
	int restarter_spec;

	/*
	 * Use the restarter specific get pids routine, if available.
	 * Only check for non-legacy services (wip->pg == 0).
	 */
	if (wip->pg != NULL) {
		r = pg_get_single_val(wip->pg, scf_property_contract,
		    SCF_TYPE_COUNT, &c, 0, EMPTY_OK | MULTI_OK);
	} else {
		r = ctids_by_restarter(wip, &c, 0, MULTI_OK, &restarter_spec,
		    NULL, NULL);
		if (restarter_spec == 0) {
			/* No restarter specific routine */
			r = get_restarter_count_prop(wip->inst,
			    scf_property_contract, &c, EMPTY_OK | MULTI_OK);
		}
	}

	if (r == 0)
		(void) snprintf(newbuf, newsize, "%s%*lu ",
		    *buf ? *buf : "", CTID_COLUMN_WIDTH, (ctid_t)c);
	else if (r == E2BIG)
		(void) snprintf(newbuf, newsize, "%s%*lu* ",
		    *buf ? *buf : "", CTID_COLUMN_WIDTH - 1, (ctid_t)c);
	else
		(void) snprintf(newbuf, newsize, "%s%*s ",
		    *buf ? *buf : "", CTID_COLUMN_WIDTH, "-");
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

#define	CTID_SORTKEY_WIDTH		(sizeof (uint64_t))

static void
sortkey_ctid(char *buf, int reverse, scf_walkinfo_t *wip)
{
	int r;
	uint64_t c;
	int restarter_spec;

	/*
	 * Use the restarter specific get pids routine, if available.
	 * Only check for non-legacy services (wip->pg == 0).
	 */
	if (wip->pg != NULL) {
		r = pg_get_single_val(wip->pg, scf_property_contract,
		    SCF_TYPE_COUNT, &c, 0, EMPTY_OK);
	} else {
		r = ctids_by_restarter(wip, &c, 0, MULTI_OK, &restarter_spec,
		    NULL, NULL);
		if (restarter_spec == 0) {
			/* No restarter specific routine */
			r = get_restarter_count_prop(wip->inst,
			    scf_property_contract, &c, EMPTY_OK);
		}
	}

	if (r == 0) {
		/*
		 * Use the id itself, but it must be big-endian for this to
		 * work.
		 */
		c = BE_64(c);

		bcopy(&c, buf, CTID_SORTKEY_WIDTH);
	} else {
		bzero(buf, CTID_SORTKEY_WIDTH);
	}

	if (reverse)
		reverse_bytes(buf, CTID_SORTKEY_WIDTH);
}

/* DESC */
#define	DESC_COLUMN_WIDTH	100

static void
sprint_desc(char **buf, scf_walkinfo_t *wip)
{
	char *x;
	size_t newsize;
	char *newbuf;

	if (common_name_buf == NULL)
		common_name_buf = safe_malloc(max_scf_value_length + 1);

	bzero(common_name_buf, max_scf_value_length + 1);

	if (wip->pg != NULL) {
		common_name_buf[0] = '-';
	} else if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, locale,
	    SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0,
	    1, 1) == -1 &&
	    inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, "C",
	    SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0,
	    1, 1) == -1) {
		common_name_buf[0] = '-';
	}

	/*
	 * Collapse multi-line tm_common_name values into a single line.
	 */
	for (x = common_name_buf; *x != '\0'; x++)
		if (*x == '\n')
			*x = ' ';

	if (strlen(common_name_buf) > DESC_COLUMN_WIDTH)
		newsize = (*buf ? strlen(*buf) : 0) +
		    strlen(common_name_buf) + 1;
	else
		newsize = (*buf ? strlen(*buf) : 0) + DESC_COLUMN_WIDTH + 1;
	newbuf = safe_malloc(newsize);
	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    DESC_COLUMN_WIDTH, common_name_buf);
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

/* ARGSUSED */
static void
sortkey_desc(char *buf, int reverse, scf_walkinfo_t *wip)
{
	bzero(buf, DESC_COLUMN_WIDTH);
}

/* State columns (STATE, NSTATE, S, N, SN, STA, NSTA) */

static char
state_to_char(const char *state)
{
	if (strcmp(state, SCF_STATE_STRING_UNINIT) == 0)
		return ('u');

	if (strcmp(state, SCF_STATE_STRING_OFFLINE) == 0)
		return ('0');

	if (strcmp(state, SCF_STATE_STRING_ONLINE) == 0)
		return ('1');

	if (strcmp(state, SCF_STATE_STRING_MAINT) == 0)
		return ('m');

	if (strcmp(state, SCF_STATE_STRING_DISABLED) == 0)
		return ('d');

	if (strcmp(state, SCF_STATE_STRING_DEGRADED) == 0)
		return ('D');

	if (strcmp(state, SCF_STATE_STRING_LEGACY) == 0)
		return ('L');

	return ('?');
}

/* Return true if inst is transitioning. */
static int
transitioning(scf_instance_t *inst)
{
	char nstate_name[MAX_SCF_STATE_STRING_SZ];

	get_restarter_string_prop(inst, scf_property_next_state, nstate_name,
	    sizeof (nstate_name));

	return (state_to_char(nstate_name) != '?');
}

/* ARGSUSED */
static void
sortkey_states(const char *pname, char *buf, int reverse, scf_walkinfo_t *wip)
{
	char state_name[MAX_SCF_STATE_STRING_SZ];

	/*
	 * Lower numbers are printed first, so these are arranged from least
	 * interesting ("legacy run") to most interesting (unknown).
	 */
	if (wip->pg == NULL) {
		get_restarter_string_prop(wip->inst, pname, state_name,
		    sizeof (state_name));

		if (strcmp(state_name, SCF_STATE_STRING_ONLINE) == 0)
			*buf = 2;
		else if (strcmp(state_name, SCF_STATE_STRING_DEGRADED) == 0)
			*buf = 3;
		else if (strcmp(state_name, SCF_STATE_STRING_OFFLINE) == 0)
			*buf = 4;
		else if (strcmp(state_name, SCF_STATE_STRING_MAINT) == 0)
			*buf = 5;
		else if (strcmp(state_name, SCF_STATE_STRING_DISABLED) == 0)
			*buf = 1;
		else if (strcmp(state_name, SCF_STATE_STRING_UNINIT) == 0)
			*buf = 6;
		else
			*buf = 7;
	} else
		*buf = 0;

	if (reverse)
		*buf = 255 - *buf;
}

static void
sprint_state(char **buf, scf_walkinfo_t *wip)
{
	char state_name[MAX_SCF_STATE_STRING_SZ + 1];
	size_t newsize;
	char *newbuf;

	if (wip->pg == NULL) {
		get_restarter_string_prop(wip->inst, scf_property_state,
		    state_name, sizeof (state_name));

		/* Don't print blank fields, to ease parsing. */
		if (state_name[0] == '\0') {
			state_name[0] = '-';
			state_name[1] = '\0';
		}

		if (!opt_nstate_shown && transitioning(wip->inst)) {
			/* Append an asterisk if nstate is valid. */
			(void) strcat(state_name, "*");
		}
	} else
		(void) strcpy(state_name, SCF_STATE_STRING_LEGACY);

	newsize = (*buf ? strlen(*buf) : 0) + MAX_SCF_STATE_STRING_SZ + 2;
	newbuf = safe_malloc(newsize);
	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    MAX_SCF_STATE_STRING_SZ + 1, state_name);

	if (*buf)
		free(*buf);
	*buf = newbuf;
}

static void
sortkey_state(char *buf, int reverse, scf_walkinfo_t *wip)
{
	sortkey_states(scf_property_state, buf, reverse, wip);
}

static void
sprint_nstate(char **buf, scf_walkinfo_t *wip)
{
	char next_state_name[MAX_SCF_STATE_STRING_SZ];
	boolean_t blank = 0;
	size_t newsize;
	char *newbuf;

	if (wip->pg == NULL) {
		get_restarter_string_prop(wip->inst, scf_property_next_state,
		    next_state_name, sizeof (next_state_name));

		/* Don't print blank fields, to ease parsing. */
		if (next_state_name[0] == '\0' ||
		    strcmp(next_state_name, SCF_STATE_STRING_NONE) == 0)
			blank = 1;
	} else
		blank = 1;

	if (blank) {
		next_state_name[0] = '-';
		next_state_name[1] = '\0';
	}

	newsize = (*buf ? strlen(*buf) : 0) + MAX_SCF_STATE_STRING_SZ + 1;
	newbuf = safe_malloc(newsize);
	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    MAX_SCF_STATE_STRING_SZ - 1, next_state_name);
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

static void
sortkey_nstate(char *buf, int reverse, scf_walkinfo_t *wip)
{
	sortkey_states(scf_property_next_state, buf, reverse, wip);
}

static void
sprint_s(char **buf, scf_walkinfo_t *wip)
{
	char tmp[3];
	char state_name[MAX_SCF_STATE_STRING_SZ];
	size_t newsize = (*buf ? strlen(*buf) : 0) + 4;
	char *newbuf = safe_malloc(newsize);

	if (wip->pg == NULL) {
		get_restarter_string_prop(wip->inst, scf_property_state,
		    state_name, sizeof (state_name));
		tmp[0] = state_to_char(state_name);

		if (!opt_nstate_shown && transitioning(wip->inst))
			tmp[1] = '*';
		else
			tmp[1] = ' ';
	} else {
		tmp[0] = 'L';
		tmp[1] = ' ';
	}
	tmp[2] = ' ';
	(void) snprintf(newbuf, newsize, "%s%-*s", *buf ? *buf : "",
	    3, tmp);
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

static void
sprint_n(char **buf, scf_walkinfo_t *wip)
{
	char tmp[2];
	size_t newsize = (*buf ? strlen(*buf) : 0) + 3;
	char *newbuf = safe_malloc(newsize);
	char nstate_name[MAX_SCF_STATE_STRING_SZ];

	if (wip->pg == NULL) {
		get_restarter_string_prop(wip->inst, scf_property_next_state,
		    nstate_name, sizeof (nstate_name));

		if (strcmp(nstate_name, SCF_STATE_STRING_NONE) == 0)
			tmp[0] = '-';
		else
			tmp[0] = state_to_char(nstate_name);
	} else
		tmp[0] = '-';

	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    2, tmp);
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

static void
sprint_sn(char **buf, scf_walkinfo_t *wip)
{
	char tmp[3];
	size_t newsize = (*buf ? strlen(*buf) : 0) + 4;
	char *newbuf = safe_malloc(newsize);
	char nstate_name[MAX_SCF_STATE_STRING_SZ];
	char state_name[MAX_SCF_STATE_STRING_SZ];

	if (wip->pg == NULL) {
		get_restarter_string_prop(wip->inst, scf_property_state,
		    state_name, sizeof (state_name));
		get_restarter_string_prop(wip->inst, scf_property_next_state,
		    nstate_name, sizeof (nstate_name));
		tmp[0] = state_to_char(state_name);

		if (strcmp(nstate_name, SCF_STATE_STRING_NONE) == 0)
			tmp[1] = '-';
		else
			tmp[1] = state_to_char(nstate_name);
	} else {
		tmp[0] = 'L';
		tmp[1] = '-';
	}

	tmp[2] = ' ';
	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    3, tmp);
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

/* ARGSUSED */
static void
sortkey_sn(char *buf, int reverse, scf_walkinfo_t *wip)
{
	sortkey_state(buf, reverse, wip);
	sortkey_nstate(buf + 1, reverse, wip);
}

static const char *
state_abbrev(const char *state)
{
	if (strcmp(state, SCF_STATE_STRING_UNINIT) == 0)
		return ("UN");
	if (strcmp(state, SCF_STATE_STRING_OFFLINE) == 0)
		return ("OFF");
	if (strcmp(state, SCF_STATE_STRING_ONLINE) == 0)
		return ("ON");
	if (strcmp(state, SCF_STATE_STRING_MAINT) == 0)
		return ("MNT");
	if (strcmp(state, SCF_STATE_STRING_DISABLED) == 0)
		return ("DIS");
	if (strcmp(state, SCF_STATE_STRING_DEGRADED) == 0)
		return ("DGD");
	if (strcmp(state, SCF_STATE_STRING_LEGACY) == 0)
		return ("LRC");

	return ("?");
}

static void
sprint_sta(char **buf, scf_walkinfo_t *wip)
{
	char state_name[MAX_SCF_STATE_STRING_SZ];
	char sta[5];
	size_t newsize = (*buf ? strlen(*buf) : 0) + 6;
	char *newbuf = safe_malloc(newsize);

	if (wip->pg == NULL)
		get_restarter_string_prop(wip->inst, scf_property_state,
		    state_name, sizeof (state_name));
	else
		(void) strcpy(state_name, SCF_STATE_STRING_LEGACY);

	(void) strcpy(sta, state_abbrev(state_name));

	if (wip->pg == NULL && !opt_nstate_shown && transitioning(wip->inst))
		(void) strcat(sta, "*");

	(void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "", sta);
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

static void
sprint_nsta(char **buf, scf_walkinfo_t *wip)
{
	char state_name[MAX_SCF_STATE_STRING_SZ];
	size_t newsize = (*buf ? strlen(*buf) : 0) + 6;
	char *newbuf = safe_malloc(newsize);

	if (wip->pg == NULL)
		get_restarter_string_prop(wip->inst, scf_property_next_state,
		    state_name, sizeof (state_name));
	else
		(void) strcpy(state_name, SCF_STATE_STRING_NONE);

	if (strcmp(state_name, SCF_STATE_STRING_NONE) == 0)
		(void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "",
		    "-");
	else
		(void) snprintf(newbuf, newsize, "%s%-4s ", *buf ? *buf : "",
		    state_abbrev(state_name));
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

/* FMRI */
#define	FMRI_COLUMN_WIDTH	50
static void
sprint_fmri(char **buf, scf_walkinfo_t *wip)
{
	char *fmri_buf = safe_malloc(max_scf_fmri_length + 1);
	size_t newsize;
	char *newbuf;

	if (wip->pg == NULL) {
		if (scf_instance_to_fmri(wip->inst, fmri_buf,
		    max_scf_fmri_length + 1) == -1)
			scfdie();
	} else {
		(void) strcpy(fmri_buf, SCF_FMRI_LEGACY_PREFIX);
		if (pg_get_single_val(wip->pg, SCF_LEGACY_PROPERTY_NAME,
		    SCF_TYPE_ASTRING, fmri_buf +
		    sizeof (SCF_FMRI_LEGACY_PREFIX) - 1,
		    max_scf_fmri_length + 1 -
		    (sizeof (SCF_FMRI_LEGACY_PREFIX) - 1), 0) != 0)
			(void) strcat(fmri_buf, LEGACY_UNKNOWN);
	}

	if (strlen(fmri_buf) > FMRI_COLUMN_WIDTH)
		newsize = (*buf ? strlen(*buf) : 0) + strlen(fmri_buf) + 2;
	else
		newsize = (*buf ? strlen(*buf) : 0) + FMRI_COLUMN_WIDTH + 2;
	newbuf = safe_malloc(newsize);
	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    FMRI_COLUMN_WIDTH, fmri_buf);
	free(fmri_buf);
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

static void
sortkey_fmri(char *buf, int reverse, scf_walkinfo_t *wip)
{
	char *tmp = NULL;

	sprint_fmri(&tmp, wip);
	bcopy(tmp, buf, FMRI_COLUMN_WIDTH);
	free(tmp);
	if (reverse)
		reverse_bytes(buf, FMRI_COLUMN_WIDTH);
}

/* Component columns */
#define	COMPONENT_COLUMN_WIDTH	20
static void
sprint_scope(char **buf, scf_walkinfo_t *wip)
{
	char *scope_buf = safe_malloc(max_scf_name_length + 1);
	size_t newsize = (*buf ? strlen(*buf) : 0) + COMPONENT_COLUMN_WIDTH + 2;
	char *newbuf = safe_malloc(newsize);

	assert(wip->scope != NULL);

	if (scf_scope_get_name(wip->scope, scope_buf, max_scf_name_length) < 0)
		scfdie();

	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    COMPONENT_COLUMN_WIDTH, scope_buf);
	if (*buf)
		free(*buf);
	*buf = newbuf;
	free(scope_buf);
}

static void
sortkey_scope(char *buf, int reverse, scf_walkinfo_t *wip)
{
	char *tmp = NULL;

	sprint_scope(&tmp, wip);
	bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH);
	free(tmp);
	if (reverse)
		reverse_bytes(buf, COMPONENT_COLUMN_WIDTH);
}

static void
sprint_service(char **buf, scf_walkinfo_t *wip)
{
	char *svc_buf = safe_malloc(max_scf_name_length + 1);
	char *newbuf;
	size_t newsize;

	if (wip->pg == NULL) {
		if (scf_service_get_name(wip->svc, svc_buf,
		    max_scf_name_length + 1) < 0)
			scfdie();
	} else {
		if (pg_get_single_val(wip->pg, "name", SCF_TYPE_ASTRING,
		    svc_buf, max_scf_name_length + 1, EMPTY_OK) != 0)
			(void) strcpy(svc_buf, LEGACY_UNKNOWN);
	}


	if (strlen(svc_buf) > COMPONENT_COLUMN_WIDTH)
		newsize = (*buf ? strlen(*buf) : 0) + strlen(svc_buf) + 2;
	else
		newsize = (*buf ? strlen(*buf) : 0) +
		    COMPONENT_COLUMN_WIDTH + 2;
	newbuf = safe_malloc(newsize);
	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    COMPONENT_COLUMN_WIDTH, svc_buf);
	free(svc_buf);
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

static void
sortkey_service(char *buf, int reverse, scf_walkinfo_t *wip)
{
	char *tmp = NULL;

	sprint_service(&tmp, wip);
	bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH);
	free(tmp);
	if (reverse)
		reverse_bytes(buf, COMPONENT_COLUMN_WIDTH);
}

/* INST */
static void
sprint_instance(char **buf, scf_walkinfo_t *wip)
{
	char *tmp = safe_malloc(max_scf_name_length + 1);
	size_t newsize = (*buf ? strlen(*buf) : 0) + COMPONENT_COLUMN_WIDTH + 2;
	char *newbuf = safe_malloc(newsize);

	if (wip->pg == NULL) {
		if (scf_instance_get_name(wip->inst, tmp,
		    max_scf_name_length + 1) < 0)
			scfdie();
	} else {
		tmp[0] = '-';
		tmp[1] = '\0';
	}

	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    COMPONENT_COLUMN_WIDTH, tmp);
	if (*buf)
		free(*buf);
	*buf = newbuf;
	free(tmp);
}

static void
sortkey_instance(char *buf, int reverse, scf_walkinfo_t *wip)
{
	char *tmp = NULL;

	sprint_instance(&tmp, wip);
	bcopy(tmp, buf, COMPONENT_COLUMN_WIDTH);
	free(tmp);
	if (reverse)
		reverse_bytes(buf, COMPONENT_COLUMN_WIDTH);
}

/* STIME */
#define	STIME_COLUMN_WIDTH		8
#define	FORMAT_TIME			"%k:%M:%S"
#define	FORMAT_DATE			"%b_%d  "
#define	FORMAT_YEAR			"%Y    "

/*
 * sprint_stime() will allocate a new buffer and snprintf the services's
 * state timestamp.  If the timestamp is unavailable for some reason
 * a '-' is given instead.
 */
static void
sprint_stime(char **buf, scf_walkinfo_t *wip)
{
	int r;
	struct timeval tv;
	time_t then;
	struct tm *tm;
	char st_buf[STIME_COLUMN_WIDTH + 1];
	size_t newsize = (*buf ? strlen(*buf) : 0) + STIME_COLUMN_WIDTH + 2;
	char *newbuf = safe_malloc(newsize);

	if (wip->pg == NULL) {
		r = get_restarter_time_prop(wip->inst,
		    SCF_PROPERTY_STATE_TIMESTAMP, &tv, 0);
	} else {
		r = pg_get_single_val(wip->pg, SCF_PROPERTY_STATE_TIMESTAMP,
		    SCF_TYPE_TIME, &tv, 0, 0);
	}

	if (r != 0) {
		/*
		 * There's something amiss with our service
		 * so we'll print a '-' for STIME.
		 */
		(void) snprintf(newbuf, newsize, "%s%-*s", *buf ? *buf : "",
		    STIME_COLUMN_WIDTH + 1, "-");
	} else {
		/* tv should be valid so we'll format it */
		then = (time_t)tv.tv_sec;

		tm = localtime(&then);
		/*
		 * Print time if started within the past 24 hours, print date
		 * if within the past 12 months or, finally, print year if
		 * started greater than 12 months ago.
		 */
		if (now - then < 24 * 60 * 60) {
			(void) strftime(st_buf, sizeof (st_buf),
			    gettext(FORMAT_TIME), tm);
		} else if (now - then < 12 * 30 * 24 * 60 * 60) {
			(void) strftime(st_buf, sizeof (st_buf),
			    gettext(FORMAT_DATE), tm);
		} else {
			(void) strftime(st_buf, sizeof (st_buf),
			    gettext(FORMAT_YEAR), tm);
		}
		(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
		    STIME_COLUMN_WIDTH + 1, st_buf);
	}
	if (*buf)
		free(*buf);
	*buf = newbuf;
}

#define	STIME_SORTKEY_WIDTH		(sizeof (uint64_t) + sizeof (uint32_t))

/* ARGSUSED */
static void
sortkey_stime(char *buf, int reverse, scf_walkinfo_t *wip)
{
	struct timeval tv;
	int r;

	if (wip->pg == NULL)
		r = get_restarter_time_prop(wip->inst,
		    SCF_PROPERTY_STATE_TIMESTAMP, &tv, 0);
	else
		r = pg_get_single_val(wip->pg, SCF_PROPERTY_STATE_TIMESTAMP,
		    SCF_TYPE_TIME, &tv, 0, 0);

	if (r == 0) {
		int64_t sec;
		int32_t us;

		/* Stick it straight into the buffer. */
		sec = tv.tv_sec;
		us = tv.tv_usec;

		sec = BE_64(sec);
		us = BE_32(us);
		bcopy(&sec, buf, sizeof (sec));
		bcopy(&us, buf + sizeof (sec), sizeof (us));
	} else {
		bzero(buf, STIME_SORTKEY_WIDTH);
	}

	if (reverse)
		reverse_bytes(buf, STIME_SORTKEY_WIDTH);
}

/* ZONE */
#define	ZONE_COLUMN_WIDTH	16
/*ARGSUSED*/
static void
sprint_zone(char **buf, scf_walkinfo_t *wip)
{
	size_t newsize;
	char *newbuf, *zonename = g_zonename, b[ZONENAME_MAX];

	if (zonename == NULL) {
		zoneid_t zoneid = getzoneid();

		if (getzonenamebyid(zoneid, b, sizeof (b)) < 0)
			uu_die(gettext("could not determine zone name"));

		zonename = b;
	}

	if (strlen(zonename) > ZONE_COLUMN_WIDTH)
		newsize = (*buf ? strlen(*buf) : 0) + strlen(zonename) + 2;
	else
		newsize = (*buf ? strlen(*buf) : 0) + ZONE_COLUMN_WIDTH + 2;

	newbuf = safe_malloc(newsize);
	(void) snprintf(newbuf, newsize, "%s%-*s ", *buf ? *buf : "",
	    ZONE_COLUMN_WIDTH, zonename);

	if (*buf)
		free(*buf);
	*buf = newbuf;
}

static void
sortkey_zone(char *buf, int reverse, scf_walkinfo_t *wip)
{
	char *tmp = NULL;

	sprint_zone(&tmp, wip);
	bcopy(tmp, buf, ZONE_COLUMN_WIDTH);
	free(tmp);
	if (reverse)
		reverse_bytes(buf, ZONE_COLUMN_WIDTH);
}

/*
 * Information about columns which can be displayed.  If you add something,
 * check MAX_COLUMN_NAME_LENGTH_STR & update description_of_column() below.
 */
static const struct column columns[] = {
	{ "CTID", CTID_COLUMN_WIDTH, sprint_ctid,
		CTID_SORTKEY_WIDTH, sortkey_ctid },
	{ "DESC", DESC_COLUMN_WIDTH, sprint_desc,
		DESC_COLUMN_WIDTH, sortkey_desc },
	{ "FMRI", FMRI_COLUMN_WIDTH, sprint_fmri,
		FMRI_COLUMN_WIDTH, sortkey_fmri },
	{ "INST", COMPONENT_COLUMN_WIDTH, sprint_instance,
		COMPONENT_COLUMN_WIDTH, sortkey_instance },
	{ "N", 1,  sprint_n, 1, sortkey_nstate },
	{ "NSTA", 4, sprint_nsta, 1, sortkey_nstate },
	{ "NSTATE", MAX_SCF_STATE_STRING_SZ - 1, sprint_nstate,
		1, sortkey_nstate },
	{ "S", 2, sprint_s, 1, sortkey_state },
	{ "SCOPE", COMPONENT_COLUMN_WIDTH, sprint_scope,
		COMPONENT_COLUMN_WIDTH, sortkey_scope },
	{ "SN", 2, sprint_sn, 2, sortkey_sn },
	{ "SVC", COMPONENT_COLUMN_WIDTH, sprint_service,
		COMPONENT_COLUMN_WIDTH, sortkey_service },
	{ "STA", 4, sprint_sta, 1, sortkey_state },
	{ "STATE", MAX_SCF_STATE_STRING_SZ - 1 + 1, sprint_state,
		1, sortkey_state },
	{ "STIME", STIME_COLUMN_WIDTH, sprint_stime,
		STIME_SORTKEY_WIDTH, sortkey_stime },
	{ "ZONE", ZONE_COLUMN_WIDTH, sprint_zone,
		ZONE_COLUMN_WIDTH, sortkey_zone },
};

#define	MAX_COLUMN_NAME_LENGTH_STR	"6"

static const int ncolumns = sizeof (columns) / sizeof (columns[0]);

/*
 * Necessary thanks to gettext() & xgettext.
 */
static const char *
description_of_column(int c)
{
	const char *s = NULL;

	switch (c) {
	case 0:
		s = gettext("contract ID for service (see contract(5))");
		break;
	case 1:
		s = gettext("human-readable description of the service");
		break;
	case 2:
		s = gettext("Fault Managed Resource Identifier for service");
		break;
	case 3:
		s = gettext("portion of the FMRI indicating service instance");
		break;
	case 4:
		s = gettext("abbreviation for next state (if in transition)");
		break;
	case 5:
		s = gettext("abbreviation for next state (if in transition)");
		break;
	case 6:
		s = gettext("name for next state (if in transition)");
		break;
	case 7:
		s = gettext("abbreviation for current state");
		break;
	case 8:
		s = gettext("name for scope associated with service");
		break;
	case 9:
		s = gettext("abbreviation for current state and next state");
		break;
	case 10:
		s = gettext("portion of the FMRI representing service name");
		break;
	case 11:
		s = gettext("abbreviation for current state");
		break;
	case 12:
		s = gettext("name for current state");
		break;
	case 13:
		s = gettext("time of last state change");
		break;
	case 14:
		s = gettext("name of zone");
		break;
	}

	assert(s != NULL);
	return (s);
}


static void
print_usage(const char *progname, FILE *f)
{
	(void) fprintf(f, gettext(
	    "Usage: %1$s [-aHpv] [-o col[,col ... ]] [-R restarter] "
	    "[-sS col] [-Z | -z zone ]\n            [<service> ...]\n"
	    "       %1$s -d | -D [-Hpv] [-o col[,col ... ]] [-sS col] "
	    "[-Z | -z zone ]\n            [<service> ...]\n"
	    "       %1$s [-l | -L] [-Z | -z zone] <service> ...\n"
	    "       %1$s -x [-v] [-Z | -z zone] [<service> ...]\n"
	    "       %1$s -?\n"), progname);
}

static __NORETURN void
argserr(const char *progname)
{
	print_usage(progname, stderr);
	exit(UU_EXIT_USAGE);
}

static void
print_help(const char *progname)
{
	int i;

	print_usage(progname, stdout);

	(void) printf(gettext("\n"
	"\t-a  list all service instances rather than "
	"only those that are enabled\n"
	"\t-d  list dependencies of the specified service(s)\n"
	"\t-D  list dependents of the specified service(s)\n"
	"\t-H  omit header line from output\n"
	"\t-l  list detailed information about the specified service(s)\n"
	"\t-L  list the log file associated with the specified service(s)\n"
	"\t-o  list only the specified columns in the output\n"
	"\t-p  list process IDs and names associated with each service\n"
	"\t-R  list only those services with the specified restarter\n"
	"\t-s  sort output in ascending order by the specified column(s)\n"
	"\t-S  sort output in descending order by the specified column(s)\n"
	"\t-v  list verbose information appropriate to the type of output\n"
	"\t-x  explain the status of services that might require maintenance,\n"
	"\t    or explain the status of the specified service(s)\n"
	"\t-z  from global zone, show services in a specified zone\n"
	"\t-Z  from global zone, show services in all zones\n"
	"\n\t"
	"Services can be specified using an FMRI, abbreviation, or fnmatch(7)\n"
	"\tpattern, as shown in these examples for svc:/network/smtp:sendmail\n"
	"\n"
	"\t%1$s [opts] svc:/network/smtp:sendmail\n"
	"\t%1$s [opts] network/smtp:sendmail\n"
	"\t%1$s [opts] network/*mail\n"
	"\t%1$s [opts] network/smtp\n"
	"\t%1$s [opts] smtp:sendmail\n"
	"\t%1$s [opts] smtp\n"
	"\t%1$s [opts] sendmail\n"
	"\n\t"
	"Columns for output or sorting can be specified using these names:\n"
	"\n"), progname);

	for (i = 0; i < ncolumns; i++) {
		(void) printf("\t%-" MAX_COLUMN_NAME_LENGTH_STR "s  %s\n",
		    columns[i].name, description_of_column(i));
	}
}


/*
 * A getsubopt()-like function which returns an index into the columns table.
 * On success, *optionp is set to point to the next sub-option, or the
 * terminating null if there are none.
 */
static int
getcolumnopt(char **optionp)
{
	char *str = *optionp, *cp;
	int i;

	assert(optionp != NULL);
	assert(*optionp != NULL);

	cp = strchr(*optionp, ',');
	if (cp != NULL)
		*cp = '\0';

	for (i = 0; i < ncolumns; ++i) {
		if (strcasecmp(str, columns[i].name) == 0) {
			if (cp != NULL)
				*optionp = cp + 1;
			else
				*optionp = strchr(*optionp, '\0');

			return (i);
		}
	}

	return (-1);
}

static void
print_header()
{
	int i;
	char *line_buf, *cp;

	line_buf = safe_malloc(line_sz);
	cp = line_buf;
	for (i = 0; i < opt_cnum; ++i) {
		const struct column * const colp = &columns[opt_columns[i]];

		(void) snprintf(cp, colp->width + 1, "%-*s", colp->width,
		    colp->name);
		cp += colp->width;
		*cp++ = ' ';
	}

	/* Trim the trailing whitespace */
	--cp;
	while (*cp == ' ')
		--cp;
	*(cp+1) = '\0';
	(void) puts(line_buf);

	free(line_buf);
}



/*
 * Long listing (-l) functions.
 */

static int
pidcmp(const void *l, const void *r)
{
	pid_t lp = *(pid_t *)l, rp = *(pid_t *)r;

	if (lp < rp)
		return (-1);
	if (lp > rp)
		return (1);
	return (0);
}

/*
 * This is the strlen() of the longest label ("description"), plus intercolumn
 * space.
 */
#define	DETAILED_WIDTH	(11 + 2)

/*
 * Callback routine to print header for contract id.
 * Called by ctids_by_restarter and print_detailed.
 */
static void
print_ctid_header()
{
	(void) printf("%-*s", DETAILED_WIDTH, "contract_id");
}

/*
 * Callback routine to print a contract id.
 * Called by ctids_by_restarter and print_detailed.
 */
static void
print_ctid_detailed(uint64_t c)
{
	(void) printf("%lu ", (ctid_t)c);
}

static void
detailed_list_processes(scf_walkinfo_t *wip)
{
	uint64_t c;
	pid_t *pids;
	uint_t i, n;
	psinfo_t psi;

	if (get_restarter_count_prop(wip->inst, scf_property_contract, &c,
	    EMPTY_OK) != 0)
		return;

	if (instance_processes(wip->inst, wip->fmri, &pids, &n) != 0)
		return;

	qsort(pids, n, sizeof (*pids), pidcmp);

	for (i = 0; i < n; ++i) {
		(void) printf("%-*s%lu", DETAILED_WIDTH, gettext("process"),
		    pids[i]);

		if (proc_get_psinfo(pids[i], &psi) == 0 && !IS_ZOMBIE(&psi))
			(void) printf(" %.*s", PRARGSZ, psi.pr_psargs);

		(void) putchar('\n');
	}

	free(pids);
}

/*
 * Determines the state of a dependency.  If the FMRI specifies a file, then we
 * fake up a state based on whether we can access the file.
 */
static void
get_fmri_state(char *fmri, char *state, size_t state_sz)
{
	char *lfmri;
	const char *svc_name, *inst_name, *pg_name, *path;
	scf_service_t *svc;
	scf_instance_t *inst;
	scf_iter_t *iter;

	lfmri = safe_strdup(fmri);

	/*
	 * Check for file:// dependencies
	 */
	if (scf_parse_file_fmri(lfmri, NULL, &path) == SCF_SUCCESS) {
		struct stat64 statbuf;
		const char *msg;

		if (stat64(path, &statbuf) == 0)
			msg = "online";
		else if (errno == ENOENT)
			msg = "absent";
		else
			msg = "unknown";

		(void) strlcpy(state, msg, state_sz);
		return;
	}

	/*
	 * scf_parse_file_fmri() may have overwritten part of the string, so
	 * copy it back.
	 */
	(void) strcpy(lfmri, fmri);

	if (scf_parse_svc_fmri(lfmri, NULL, &svc_name, &inst_name,
	    &pg_name, NULL) != SCF_SUCCESS) {
		free(lfmri);
		(void) strlcpy(state, "invalid", state_sz);
		return;
	}

	free(lfmri);

	if (svc_name == NULL || pg_name != NULL) {
		(void) strlcpy(state, "invalid", state_sz);
		return;
	}

	if (inst_name != NULL) {
		/* instance: get state */
		inst = scf_instance_create(h);
		if (inst == NULL)
			scfdie();

		if (scf_handle_decode_fmri(h, fmri, NULL, NULL, inst, NULL,
		    NULL, SCF_DECODE_FMRI_EXACT) == SCF_SUCCESS)
			get_restarter_string_prop(inst, scf_property_state,
			    state, state_sz);
		else {
			switch (scf_error()) {
			case SCF_ERROR_INVALID_ARGUMENT:
				(void) strlcpy(state, "invalid", state_sz);
				break;
			case SCF_ERROR_NOT_FOUND:
				(void) strlcpy(state, "absent", state_sz);
				break;

			default:
				scfdie();
			}
		}

		scf_instance_destroy(inst);
		return;
	}

	/*
	 * service: If only one instance, use that state.  Otherwise, say
	 * "multiple".
	 */
	if ((svc = scf_service_create(h)) == NULL ||
	    (inst = scf_instance_create(h)) == NULL ||
	    (iter = scf_iter_create(h)) == NULL)
		scfdie();

	if (scf_handle_decode_fmri(h, fmri, NULL, svc, NULL, NULL, NULL,
	    SCF_DECODE_FMRI_EXACT) != SCF_SUCCESS) {
		switch (scf_error()) {
		case SCF_ERROR_INVALID_ARGUMENT:
			(void) strlcpy(state, "invalid", state_sz);
			goto out;
		case SCF_ERROR_NOT_FOUND:
			(void) strlcpy(state, "absent", state_sz);
			goto out;

		default:
			scfdie();
		}
	}

	if (scf_iter_service_instances(iter, svc) != SCF_SUCCESS)
		scfdie();

	switch (scf_iter_next_instance(iter, inst)) {
	case 0:
		(void) strlcpy(state, "absent", state_sz);
		goto out;

	case 1:
		break;

	default:
		scfdie();
	}

	/* Get the state in case this is the only instance. */
	get_restarter_string_prop(inst, scf_property_state, state, state_sz);

	switch (scf_iter_next_instance(iter, inst)) {
	case 0:
		break;

	case 1:
		/* Nope, multiple instances. */
		(void) strlcpy(state, "multiple", state_sz);
		goto out;

	default:
		scfdie();
	}

out:
	scf_iter_destroy(iter);
	scf_instance_destroy(inst);
	scf_service_destroy(svc);
}

static void
print_application_properties(scf_walkinfo_t *wip, scf_snapshot_t *snap)
{
	scf_iter_t *pg_iter, *prop_iter, *val_iter;
	scf_propertygroup_t *pg;
	scf_property_t *prop;
	scf_value_t *val;
	scf_pg_tmpl_t *pt;
	scf_prop_tmpl_t *prt;
	char *pg_name_buf = safe_malloc(max_scf_name_length + 1);
	char *prop_name_buf = safe_malloc(max_scf_name_length + 1);
	char *snap_name = safe_malloc(max_scf_name_length + 1);
	char *val_buf = safe_malloc(max_scf_value_length + 1);
	char *desc, *cp;
	scf_type_t type;
	int i, j, k;
	uint8_t vis;

	if ((pg_iter = scf_iter_create(h)) == NULL ||
	    (prop_iter = scf_iter_create(h)) == NULL ||
	    (val_iter = scf_iter_create(h)) == NULL ||
	    (val = scf_value_create(h)) == NULL ||
	    (prop = scf_property_create(h)) == NULL ||
	    (pt = scf_tmpl_pg_create(h)) == NULL ||
	    (prt = scf_tmpl_prop_create(h)) == NULL ||
	    (pg = scf_pg_create(h)) == NULL)
		scfdie();

	if (scf_iter_instance_pgs_typed_composed(pg_iter, wip->inst, snap,
	    SCF_PG_APP_DEFAULT) == -1)
		scfdie();

	/*
	 * Format for output:
	 *	pg (pgtype)
	 *	 description
	 *	pg/prop (proptype) = <value> <value>
	 *	 description
	 */
	while ((i = scf_iter_next_pg(pg_iter, pg)) == 1) {
		int tmpl = 0;

		if (scf_pg_get_name(pg, pg_name_buf, max_scf_name_length) < 0)
			scfdie();
		if (scf_snapshot_get_name(snap, snap_name,
		    max_scf_name_length) < 0)
			scfdie();

		if (scf_tmpl_get_by_pg_name(wip->fmri, snap_name, pg_name_buf,
		    SCF_PG_APP_DEFAULT, pt, 0) == 0)
			tmpl = 1;
		else
			tmpl = 0;

		(void) printf("%s (%s)\n", pg_name_buf, SCF_PG_APP_DEFAULT);

		if (tmpl == 1 && scf_tmpl_pg_description(pt, NULL, &desc) > 0) {
			(void) printf("  %s\n", desc);
			free(desc);
		}

		if (scf_iter_pg_properties(prop_iter, pg) == -1)
			scfdie();
		while ((j = scf_iter_next_property(prop_iter, prop)) == 1) {
			if (scf_property_get_name(prop, prop_name_buf,
			    max_scf_name_length) < 0)
				scfdie();
			if (scf_property_type(prop, &type) == -1)
				scfdie();

			if ((tmpl == 1) &&
			    (scf_tmpl_get_by_prop(pt, prop_name_buf, prt,
			    0) != 0))
				tmpl = 0;

			if (tmpl == 1 &&
			    scf_tmpl_prop_visibility(prt, &vis) != -1 &&
			    vis == SCF_TMPL_VISIBILITY_HIDDEN)
				continue;

			(void) printf("%s/%s (%s) = ", pg_name_buf,
			    prop_name_buf, scf_type_to_string(type));

			if (scf_iter_property_values(val_iter, prop) == -1)
				scfdie();

			while ((k = scf_iter_next_value(val_iter, val)) == 1) {
				if (scf_value_get_as_string(val, val_buf,
				    max_scf_value_length + 1) < 0)
					scfdie();
				if (strpbrk(val_buf, " \t\n\"()") != NULL) {
					(void) printf("\"");
					for (cp = val_buf; *cp != '\0'; ++cp) {
						if (*cp == '"' || *cp == '\\')
							(void) putc('\\',
							    stdout);

						(void) putc(*cp, stdout);
					}
					(void) printf("\"");
				} else {
					(void) printf("%s ", val_buf);
				}
			}

			(void) printf("\n");

			if (k == -1)
				scfdie();

			if (tmpl == 1 && scf_tmpl_prop_description(prt, NULL,
			    &desc) > 0) {
				(void) printf("  %s\n", desc);
				free(desc);
			}
		}
		if (j == -1)
			scfdie();
	}
	if (i == -1)
		scfdie();


	scf_iter_destroy(pg_iter);
	scf_iter_destroy(prop_iter);
	scf_iter_destroy(val_iter);
	scf_value_destroy(val);
	scf_property_destroy(prop);
	scf_tmpl_pg_destroy(pt);
	scf_tmpl_prop_destroy(prt);
	scf_pg_destroy(pg);
	free(pg_name_buf);
	free(prop_name_buf);
	free(snap_name);
	free(val_buf);
}

static void
print_detailed_dependency(scf_propertygroup_t *pg)
{
	scf_property_t *eprop;
	scf_iter_t *iter;
	scf_type_t ty;
	char *val_buf;
	int i;

	if ((eprop = scf_property_create(h)) == NULL ||
	    (iter = scf_iter_create(h)) == NULL)
		scfdie();

	val_buf = safe_malloc(max_scf_value_length + 1);

	if (scf_pg_get_property(pg, SCF_PROPERTY_ENTITIES, eprop) !=
	    SCF_SUCCESS ||
	    scf_property_type(eprop, &ty) != SCF_SUCCESS ||
	    ty != SCF_TYPE_FMRI)
		return;

	(void) printf("%-*s", DETAILED_WIDTH, gettext("dependency"));

	/* Print the grouping */
	if (pg_get_single_val(pg, SCF_PROPERTY_GROUPING, SCF_TYPE_ASTRING,
	    val_buf, max_scf_value_length + 1, 0) == 0)
		(void) fputs(val_buf, stdout);
	else
		(void) putchar('?');

	(void) putchar('/');

	if (pg_get_single_val(pg, SCF_PROPERTY_RESTART_ON, SCF_TYPE_ASTRING,
	    val_buf, max_scf_value_length + 1, 0) == 0)
		(void) fputs(val_buf, stdout);
	else
		(void) putchar('?');

	/* Print the dependency entities. */
	if (scf_iter_property_values(iter, eprop) == -1)
		scfdie();

	while ((i = scf_iter_next_value(iter, g_val)) == 1) {
		char state[MAX_SCF_STATE_STRING_SZ];

		if (scf_value_get_astring(g_val, val_buf,
		    max_scf_value_length + 1) < 0)
			scfdie();

		(void) putchar(' ');
		(void) fputs(val_buf, stdout);

		/* Print the state. */
		state[0] = '-';
		state[1] = '\0';

		get_fmri_state(val_buf, state, sizeof (state));

		(void) printf(" (%s)", state);
	}
	if (i == -1)
		scfdie();

	(void) putchar('\n');

	free(val_buf);
	scf_iter_destroy(iter);
	scf_property_destroy(eprop);
}

/* ARGSUSED */
static int
print_detailed(void *unused, scf_walkinfo_t *wip)
{
	scf_snapshot_t *snap;
	scf_propertygroup_t *rpg;
	scf_iter_t *pg_iter;

	char *buf;
	char *timebuf;
	size_t tbsz;
	int ret;
	uint64_t c;
	int temp, perm;
	struct timeval tv;
	time_t stime;
	struct tm *tmp;
	int restarter_spec;
	int restarter_ret;

	const char * const fmt = "%-*s%s\n";

	assert(wip->pg == NULL);

	rpg = scf_pg_create(h);
	if (rpg == NULL)
		scfdie();

	if (first_paragraph)
		first_paragraph = 0;
	else
		(void) putchar('\n');

	buf = safe_malloc(max_scf_fmri_length + 1);

	if (scf_instance_to_fmri(wip->inst, buf, max_scf_fmri_length + 1) != -1)
		(void) printf(fmt, DETAILED_WIDTH, "fmri", buf);

	if (common_name_buf == NULL)
		common_name_buf = safe_malloc(max_scf_value_length + 1);

	if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, locale,
	    SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0, 1, 1)
	    == 0)
		(void) printf(fmt, DETAILED_WIDTH, gettext("name"),
		    common_name_buf);
	else if (inst_get_single_val(wip->inst, SCF_PG_TM_COMMON_NAME, "C",
	    SCF_TYPE_USTRING, common_name_buf, max_scf_value_length, 0, 1, 1)
	    == 0)
		(void) printf(fmt, DETAILED_WIDTH, gettext("name"),
		    common_name_buf);

	if (g_zonename != NULL)
		(void) printf(fmt, DETAILED_WIDTH, gettext("zone"), g_zonename);

	/*
	 * Synthesize an 'enabled' property that hides the enabled_ovr
	 * implementation from the user.  If the service has been temporarily
	 * set to a state other than its permanent value, alert the user with
	 * a '(temporary)' message.
	 */
	perm = instance_enabled(wip->inst, B_FALSE);
	temp = instance_enabled(wip->inst, B_TRUE);
	if (temp != -1) {
		if (temp != perm)
			(void) printf(gettext("%-*s%s (temporary)\n"),
			    DETAILED_WIDTH, gettext("enabled"),
			    temp ? gettext("true") : gettext("false"));
		else
			(void) printf(fmt, DETAILED_WIDTH,
			    gettext("enabled"), temp ? gettext("true") :
			    gettext("false"));
	} else if (perm != -1) {
		(void) printf(fmt, DETAILED_WIDTH, gettext("enabled"),
		    perm ? gettext("true") : gettext("false"));
	}

	if (temp == 0 || (temp == -1 && perm == 0)) {
		char comment[SCF_COMMENT_MAX_LENGTH] = "";
		const char *pg = (temp != -1 && temp != perm) ?
		    SCF_PG_GENERAL_OVR : SCF_PG_GENERAL;

		(void) inst_get_single_val(wip->inst, pg, SCF_PROPERTY_COMMENT,
		    SCF_TYPE_ASTRING, &comment, sizeof (comment),
		    EMPTY_OK, 0, 0);

		if (comment[0] != '\0') {
			printf(fmt, DETAILED_WIDTH, gettext("comment"),
			    comment);
		}
	}

	/*
	 * Property values may be longer than max_scf_fmri_length, but these
	 * shouldn't be, so we'll just reuse buf.  The user can use svcprop if
	 * they suspect something fishy.
	 */
	if (scf_instance_get_pg(wip->inst, SCF_PG_RESTARTER, rpg) != 0) {
		if (scf_error() != SCF_ERROR_NOT_FOUND)
			scfdie();

		scf_pg_destroy(rpg);
		rpg = NULL;
	}

	if (rpg) {
		if (pg_get_single_val(rpg, scf_property_state, SCF_TYPE_ASTRING,
		    buf, max_scf_fmri_length + 1, 0) == 0)
			(void) printf(fmt, DETAILED_WIDTH, gettext("state"),
			    buf);

		if (pg_get_single_val(rpg, scf_property_next_state,
		    SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0)
			(void) printf(fmt, DETAILED_WIDTH,
			    gettext("next_state"), buf);

		if (pg_get_single_val(rpg, SCF_PROPERTY_STATE_TIMESTAMP,
		    SCF_TYPE_TIME, &tv, 0, 0) == 0) {
			stime = tv.tv_sec;
			tmp = localtime(&stime);
			for (tbsz = 50; ; tbsz *= 2) {
				timebuf = safe_malloc(tbsz);
				if (strftime(timebuf, tbsz, NULL, tmp) != 0)
					break;
				free(timebuf);
			}
			(void) printf(fmt, DETAILED_WIDTH,
			    gettext("state_time"),
			    timebuf);
			free(timebuf);
		}

		if (pg_get_single_val(rpg, SCF_PROPERTY_ALT_LOGFILE,
		    SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0)
			(void) printf(fmt, DETAILED_WIDTH,
			    gettext("alt_logfile"), buf);

		if (pg_get_single_val(rpg, SCF_PROPERTY_LOGFILE,
		    SCF_TYPE_ASTRING, buf, max_scf_fmri_length + 1, 0) == 0)
			(void) printf(fmt, DETAILED_WIDTH, gettext("logfile"),
			    buf);
	}

	if (inst_get_single_val(wip->inst, SCF_PG_GENERAL,
	    SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING, buf,
	    max_scf_fmri_length + 1, 0, 0, 1) == 0)
		(void) printf(fmt, DETAILED_WIDTH, gettext("restarter"), buf);
	else
		(void) printf(fmt, DETAILED_WIDTH, gettext("restarter"),
		    SCF_SERVICE_STARTD);

	free(buf);

	/*
	 * Use the restarter specific routine to print the ctids, if available.
	 * If restarter specific action is available and it fails, then die.
	 */
	restarter_ret = ctids_by_restarter(wip, &c, 1, 0,
	    &restarter_spec, print_ctid_header, print_ctid_detailed);
	if (restarter_spec == 1) {
		if (restarter_ret != 0)
			uu_die(gettext("Unable to get restarter for %s"),
			    wip->fmri);
		goto restarter_common;
	}

	if (rpg) {
		scf_iter_t *iter;

		if ((iter = scf_iter_create(h)) == NULL)
			scfdie();

		if (scf_pg_get_property(rpg, scf_property_contract, g_prop) ==
		    0) {
			if (scf_property_is_type(g_prop, SCF_TYPE_COUNT) == 0) {

				/* Callback to print ctid header */
				print_ctid_header();

				if (scf_iter_property_values(iter, g_prop) != 0)
					scfdie();

				for (;;) {
					ret = scf_iter_next_value(iter, g_val);
					if (ret == -1)
						scfdie();
					if (ret == 0)
						break;

					if (scf_value_get_count(g_val, &c) != 0)
						scfdie();

					/* Callback to print contract id. */
					print_ctid_detailed(c);
				}

				(void) putchar('\n');
			} else {
				if (scf_error() != SCF_ERROR_TYPE_MISMATCH)
					scfdie();
			}
		} else {
			if (scf_error() != SCF_ERROR_NOT_FOUND)
				scfdie();
		}

		scf_iter_destroy(iter);
	} else {
		if (scf_error() != SCF_ERROR_NOT_FOUND)
			scfdie();
	}

restarter_common:
	scf_pg_destroy(rpg);

	/* Dependencies. */
	if ((pg_iter = scf_iter_create(h)) == NULL)
		scfdie();

	snap = get_running_snapshot(wip->inst);

	if (scf_iter_instance_pgs_typed_composed(pg_iter, wip->inst, snap,
	    SCF_GROUP_DEPENDENCY) != SCF_SUCCESS)
		scfdie();

	while ((ret = scf_iter_next_pg(pg_iter, g_pg)) == 1)
		print_detailed_dependency(g_pg);
	if (ret == -1)
		scfdie();

	scf_iter_destroy(pg_iter);

	if (opt_processes)
		detailed_list_processes(wip);

	/* "application" type property groups */
	if (opt_verbose == 1)
		print_application_properties(wip, snap);

	scf_snapshot_destroy(snap);

	return (0);
}

static int
print_log(void *unused __unused, scf_walkinfo_t *wip)
{
	scf_propertygroup_t *rpg;
	char buf[MAXPATHLEN];

	if ((rpg = scf_pg_create(h)) == NULL)
		scfdie();

	if (scf_instance_get_pg(wip->inst, SCF_PG_RESTARTER, rpg) != 0) {
		if (scf_error() != SCF_ERROR_NOT_FOUND)
			scfdie();

		goto out;
	}

	if (pg_get_single_val(rpg, SCF_PROPERTY_LOGFILE,
	    SCF_TYPE_ASTRING, buf, sizeof (buf), 0) == 0) {
		(void) printf("%s\n", buf);
	} else if (pg_get_single_val(rpg, SCF_PROPERTY_ALT_LOGFILE,
	    SCF_TYPE_ASTRING, buf, sizeof (buf), 0) == 0) {
		(void) printf("%s\n", buf);
	}

out:
	scf_pg_destroy(rpg);

	return (0);
}

int
qsort_str_compare(const void *p1, const void *p2)
{
	return (strcmp((const char *)p1, (const char *)p2));
}

/*
 * get_notify_param_classes()
 * return the fma classes that don't have a tag in fma_tags[], otherwise NULL
 */
static char **
get_notify_param_classes()
{
	scf_handle_t		*h = _scf_handle_create_and_bind(SCF_VERSION);
	scf_instance_t		*inst = scf_instance_create(h);
	scf_snapshot_t		*snap = scf_snapshot_create(h);
	scf_snaplevel_t		*slvl = scf_snaplevel_create(h);
	scf_propertygroup_t	*pg = scf_pg_create(h);
	scf_iter_t		*iter = scf_iter_create(h);
	int size = 4;
	int n = 0;
	size_t sz = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH) + 1;
	int err;
	char *pgname = safe_malloc(sz);
	char **buf = safe_malloc(size * sizeof (char *));

	if (h == NULL || inst == NULL || snap == NULL || slvl == NULL ||
	    pg == NULL || iter == NULL) {
		uu_die(gettext("Failed object creation: %s\n"),
		    scf_strerror(scf_error()));
	}

	if (scf_handle_decode_fmri(h, SCF_NOTIFY_PARAMS_INST, NULL, NULL, inst,
	    NULL, NULL, SCF_DECODE_FMRI_EXACT) != 0)
		uu_die(gettext("Failed to decode %s: %s\n"),
		    SCF_NOTIFY_PARAMS_INST, scf_strerror(scf_error()));

	if (scf_instance_get_snapshot(inst, "running", snap) != 0)
		uu_die(gettext("Failed to get snapshot: %s\n"),
		    scf_strerror(scf_error()));

	if (scf_snapshot_get_base_snaplevel(snap, slvl) != 0)
		uu_die(gettext("Failed to get base snaplevel: %s\n"),
		    scf_strerror(scf_error()));

	if (scf_iter_snaplevel_pgs_typed(iter, slvl,
	    SCF_NOTIFY_PARAMS_PG_TYPE) != 0)
		uu_die(gettext("Failed to get iterator: %s\n"),
		    scf_strerror(scf_error()));

	while ((err = scf_iter_next_pg(iter, pg)) == 1) {
		char *c;

		if (scf_pg_get_name(pg, pgname, sz) == -1)
			uu_die(gettext("Failed to get pg name: %s\n"),
			    scf_strerror(scf_error()));
		if ((c = strrchr(pgname, ',')) != NULL)
			*c = '\0';
		if (has_fma_tag(pgname))
			continue;
		if (!is_fma_token(pgname))
			/*
			 * We don't emmit a warning here so that we don't
			 * pollute the output
			 */
			continue;

		if (n + 1 >= size) {
			size *= 2;
			buf = realloc(buf, size * sizeof (char *));
			if (buf == NULL)
				uu_die(gettext("Out of memory.\n"));
		}
		buf[n] = safe_strdup(pgname);
		++n;
	}
	/*
	 * NULL terminate buf
	 */
	buf[n] = NULL;
	if (err == -1)
		uu_die(gettext("Failed to iterate pgs: %s\n"),
		    scf_strerror(scf_error()));

	/* sort the classes */
	qsort((void *)buf, n, sizeof (char *), qsort_str_compare);

	free(pgname);
	scf_iter_destroy(iter);
	scf_pg_destroy(pg);
	scf_snaplevel_destroy(slvl);
	scf_snapshot_destroy(snap);
	scf_instance_destroy(inst);
	scf_handle_destroy(h);

	return (buf);
}

/*
 * get_fma_notify_params()
 * populates an nvlist_t with notifycation parameters for a given FMA class
 * returns 0 if the nvlist is populated, 1 otherwise;
 */
int
get_fma_notify_params(nvlist_t *nvl, const char *class)
{
	if (_scf_get_fma_notify_params(class, nvl, 0) != 0) {
		/*
		 * if the preferences have just been deleted
		 * or does not exist, just skip.
		 */
		if (scf_error() != SCF_ERROR_NOT_FOUND &&
		    scf_error() != SCF_ERROR_DELETED)
			uu_warn(gettext(
			    "Failed get_fma_notify_params %s\n"),
			    scf_strerror(scf_error()));

		return (1);
	}

	return (0);
}

/*
 * print_notify_fma()
 * outputs the notification paramets of FMA events.
 * It first outputs classes in fma_tags[], then outputs the other classes
 * sorted alphabetically
 */
static void
print_notify_fma(void)
{
	nvlist_t *nvl;
	char **tmp = NULL;
	char **classes, *p;
	const char *class;
	uint32_t i;

	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
		uu_die(gettext("Out of memory.\n"));

	for (i = 0; (class = get_fma_class(i)) != NULL; ++i) {
		if (get_fma_notify_params(nvl, class) == 0)
			listnotify_print(nvl, get_fma_tag(i));
	}

	if ((classes = get_notify_param_classes()) == NULL)
		goto cleanup;

	tmp = classes;
	for (p = *tmp; p; ++tmp, p = *tmp) {
		if (get_fma_notify_params(nvl, p) == 0)
			listnotify_print(nvl, re_tag(p));

		free(p);
	}

	free(classes);

cleanup:
	nvlist_free(nvl);
}

/*
 * print_notify_fmri()
 * prints notifycation parameters for an SMF instance.
 */
static void
print_notify_fmri(const char *fmri)
{
	nvlist_t *nvl;

	if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0)
		uu_die(gettext("Out of memory.\n"));

	if (_scf_get_svc_notify_params(fmri, nvl, SCF_TRANSITION_ALL, 0, 0) !=
	    SCF_SUCCESS) {
		if (scf_error() != SCF_ERROR_NOT_FOUND &&
		    scf_error() != SCF_ERROR_DELETED)
			uu_warn(gettext(
			    "Failed _scf_get_svc_notify_params: %s\n"),
			    scf_strerror(scf_error()));
	} else {
		if (strcmp(SCF_INSTANCE_GLOBAL, fmri) == 0)
			safe_printf(
			    gettext("System wide notification parameters:\n"));
		safe_printf("%s:\n", fmri);
		listnotify_print(nvl, NULL);
	}
	nvlist_free(nvl);
}

/*
 * print_notify_special()
 * prints notification parameters for FMA events and system wide SMF state
 * transitions parameters
 */
static void
print_notify_special()
{
	safe_printf("Notification parameters for FMA Events\n");
	print_notify_fma();
	print_notify_fmri(SCF_INSTANCE_GLOBAL);
}

/*
 * print_notify()
 * callback function to print notification parameters for SMF state transition
 * instances. It skips global and notify-params instances as they should be
 * printed by print_notify_special()
 */
/* ARGSUSED */
static int
print_notify(void *unused, scf_walkinfo_t *wip)
{
	if (strcmp(SCF_INSTANCE_GLOBAL, wip->fmri) == 0 ||
	    strcmp(SCF_NOTIFY_PARAMS_INST, wip->fmri) == 0)
		return (0);

	print_notify_fmri(wip->fmri);

	return (0);
}

/*
 * Append a one-lined description of each process in inst's contract(s) and
 * return the augmented string.
 */
static char *
add_processes(scf_walkinfo_t *wip, char *line, scf_propertygroup_t *lpg)
{
	pid_t *pids = NULL;
	uint_t i, n = 0;

	if (lpg == NULL) {
		if (instance_processes(wip->inst, wip->fmri, &pids, &n) != 0)
			return (line);
	} else {
		/* Legacy services */
		scf_iter_t *iter;

		if ((iter = scf_iter_create(h)) == NULL)
			scfdie();

		(void) propvals_to_pids(lpg, scf_property_contract, &pids, &n,
		    g_prop, g_val, iter);

		scf_iter_destroy(iter);
	}

	if (n == 0)
		return (line);

	qsort(pids, n, sizeof (*pids), pidcmp);

	for (i = 0; i < n; ++i) {
		char *cp, stime[9];
		psinfo_t psi;
		const char *name = NULL;
		int len = 1 + 15 + 8 + 3 + 6 + 1 + PRFNSZ;

		if (proc_get_psinfo(pids[i], &psi) != 0)
			continue;

		line = realloc(line, strlen(line) + len);
		if (line == NULL)
			uu_die(gettext("Out of memory.\n"));

		cp = strchr(line, '\0');

		if (!IS_ZOMBIE(&psi)) {
#define	DAY (24 * 60 * 60)
#define	YEAR (12 * 30 * 24 * 60 * 60)

			struct tm *tm;

			tm = localtime(&psi.pr_start.tv_sec);

			/*
			 * Print time if started within the past 24 hours,
			 * print date if within the past 12 months, print year
			 * if started greater than 12 months ago.
			 */
			if (now - psi.pr_start.tv_sec < DAY) {
				(void) strftime(stime, sizeof (stime),
				    gettext(FORMAT_TIME), tm);
			} else if (now - psi.pr_start.tv_sec < YEAR) {
				(void) strftime(stime, sizeof (stime),
				    gettext(FORMAT_DATE), tm);
			} else {
				(void) strftime(stime, sizeof (stime),
				    gettext(FORMAT_YEAR), tm);
			}

			name = psi.pr_fname;
#undef DAY
#undef YEAR
		} else {
			(void) snprintf(stime, sizeof (stime), "-");
			name = "<defunct>";
		}

		(void) snprintf(cp, len, "\n               %-8s   %6ld %.*s",
		    stime, pids[i], PRFNSZ, name);
	}

	free(pids);

	return (line);
}

/*ARGSUSED*/
static int
list_instance(void *unused, scf_walkinfo_t *wip)
{
	struct avl_string *lp;
	char *cp;
	int i;
	uu_avl_index_t idx;

	/*
	 * If the user has specified a restarter, check for a match first
	 */
	if (restarters != NULL) {
		struct pfmri_list *rest;
		int match;
		char *restarter_fmri;
		const char *scope_name, *svc_name, *inst_name, *pg_name;

		/* legacy services don't have restarters */
		if (wip->pg != NULL)
			return (0);

		restarter_fmri = safe_malloc(max_scf_fmri_length + 1);

		if (inst_get_single_val(wip->inst, SCF_PG_GENERAL,
		    SCF_PROPERTY_RESTARTER, SCF_TYPE_ASTRING, restarter_fmri,
		    max_scf_fmri_length + 1, 0, 0, 1) != 0)
			(void) strcpy(restarter_fmri, SCF_SERVICE_STARTD);

		if (scf_parse_svc_fmri(restarter_fmri, &scope_name, &svc_name,
		    &inst_name, &pg_name, NULL) != SCF_SUCCESS) {
			free(restarter_fmri);
			return (0);
		}

		match = 0;
		for (rest = restarters; rest != NULL; rest = rest->next) {
			if (strcmp(rest->scope, scope_name) == 0 &&
			    strcmp(rest->service, svc_name) == 0 &&
			    strcmp(rest->instance, inst_name) == 0)
				match = 1;
		}

		free(restarter_fmri);

		if (!match)
			return (0);
	}

	if (wip->pg == NULL && ht_buckets != NULL && ht_add(wip->fmri)) {
		/* It was already there. */
		return (0);
	}

	lp = safe_malloc(sizeof (*lp));

	lp->str = NULL;
	for (i = 0; i < opt_cnum; ++i) {
		columns[opt_columns[i]].sprint(&lp->str, wip);
	}
	cp = lp->str + strlen(lp->str);
	cp--;
	while (*cp == ' ')
		cp--;
	*(cp+1) = '\0';

	/* If we're supposed to list the processes, too, do that now. */
	if (opt_processes)
		lp->str = add_processes(wip, lp->str, wip->pg);

	/* Create the sort key. */
	cp = lp->key = safe_malloc(sortkey_sz);
	for (i = 0; i < opt_snum; ++i) {
		int j = opt_sort[i] & 0xff;

		assert(columns[j].get_sortkey != NULL);
		columns[j].get_sortkey(cp, opt_sort[i] & ~0xff, wip);
		cp += columns[j].sortkey_width;
	}

	/* Insert into AVL tree. */
	uu_avl_node_init(lp, &lp->node, lines_pool);
	(void) uu_avl_find(lines, lp, NULL, &idx);
	uu_avl_insert(lines, lp, idx);

	return (0);
}

static int
list_if_enabled(void *unused, scf_walkinfo_t *wip)
{
	if (wip->pg != NULL ||
	    instance_enabled(wip->inst, B_FALSE) == 1 ||
	    instance_enabled(wip->inst, B_TRUE) == 1)
		return (list_instance(unused, wip));

	return (0);
}

/*
 * Service FMRI selection: Lookup and call list_instance() for the instances.
 * Instance FMRI selection: Lookup and call list_instance().
 *
 * Note: This is shoehorned into a walk_dependencies() callback prototype so
 * it can be used in list_dependencies.
 */
static int
list_svc_or_inst_fmri(void *complain, scf_walkinfo_t *wip)
{
	char *fmri;
	const char *svc_name, *inst_name, *pg_name, *save;
	scf_iter_t *iter;
	int ret;

	fmri = safe_strdup(wip->fmri);

	if (scf_parse_svc_fmri(fmri, NULL, &svc_name, &inst_name, &pg_name,
	    NULL) != SCF_SUCCESS) {
		if (complain)
			uu_warn(gettext("FMRI \"%s\" is invalid.\n"),
			    wip->fmri);
		exit_status = UU_EXIT_FATAL;
		free(fmri);
		return (0);
	}

	/*
	 * Yes, this invalidates *_name, but we only care whether they're NULL
	 * or not.
	 */
	free(fmri);

	if (svc_name == NULL || pg_name != NULL) {
		if (complain)
			uu_warn(gettext("FMRI \"%s\" does not designate a "
			    "service or instance.\n"), wip->fmri);
		return (0);
	}

	if (inst_name != NULL) {
		/* instance */
		if (scf_handle_decode_fmri(h, wip->fmri, wip->scope, wip->svc,
		    wip->inst, NULL, NULL, 0) != SCF_SUCCESS) {
			if (scf_error() != SCF_ERROR_NOT_FOUND)
				scfdie();

			if (complain)
				uu_warn(gettext(
				    "Instance \"%s\" does not exist.\n"),
				    wip->fmri);
			return (0);
		}

		return (list_instance(NULL, wip));
	}

	/* service: Walk the instances. */
	if (scf_handle_decode_fmri(h, wip->fmri, wip->scope, wip->svc, NULL,
	    NULL, NULL, 0) != SCF_SUCCESS) {
		if (scf_error() != SCF_ERROR_NOT_FOUND)
			scfdie();

		if (complain)
			uu_warn(gettext("Service \"%s\" does not exist.\n"),
			    wip->fmri);

		exit_status = UU_EXIT_FATAL;

		return (0);
	}

	iter = scf_iter_create(h);
	if (iter == NULL)
		scfdie();

	if (scf_iter_service_instances(iter, wip->svc) != SCF_SUCCESS)
		scfdie();

	if ((fmri = malloc(max_scf_fmri_length + 1)) == NULL) {
		scf_iter_destroy(iter);
		exit_status = UU_EXIT_FATAL;
		return (0);
	}

	save = wip->fmri;
	wip->fmri = fmri;
	while ((ret = scf_iter_next_instance(iter, wip->inst)) == 1) {
		if (scf_instance_to_fmri(wip->inst, fmri,
		    max_scf_fmri_length + 1) <= 0)
			scfdie();
		(void) list_instance(NULL, wip);
	}
	free(fmri);
	wip->fmri = save;
	if (ret == -1)
		scfdie();

	exit_status = UU_EXIT_OK;

	scf_iter_destroy(iter);

	return (0);
}

/*
 * Dependency selection: Straightforward since each instance lists the
 * services it depends on.
 */

static void
walk_dependencies(scf_walkinfo_t *wip, scf_walk_callback callback, void *data)
{
	scf_snapshot_t *snap;
	scf_iter_t *iter, *viter;
	int ret, vret;
	char *dep;

	assert(wip->inst != NULL);

	if ((iter = scf_iter_create(h)) == NULL ||
	    (viter = scf_iter_create(h)) == NULL)
		scfdie();

	snap = get_running_snapshot(wip->inst);

	if (scf_iter_instance_pgs_typed_composed(iter, wip->inst, snap,
	    SCF_GROUP_DEPENDENCY) != SCF_SUCCESS)
		scfdie();

	dep = safe_malloc(max_scf_value_length + 1);

	while ((ret = scf_iter_next_pg(iter, g_pg)) == 1) {
		scf_type_t ty;

		/* Ignore exclude_any dependencies. */
		if (scf_pg_get_property(g_pg, SCF_PROPERTY_GROUPING, g_prop) !=
		    SCF_SUCCESS) {
			if (scf_error() != SCF_ERROR_NOT_FOUND)
				scfdie();

			continue;
		}

		if (scf_property_type(g_prop, &ty) != SCF_SUCCESS)
			scfdie();

		if (ty != SCF_TYPE_ASTRING)
			continue;

		if (scf_property_get_value(g_prop, g_val) != SCF_SUCCESS) {
			if (scf_error() != SCF_ERROR_CONSTRAINT_VIOLATED)
				scfdie();

			continue;
		}

		if (scf_value_get_astring(g_val, dep,
		    max_scf_value_length + 1) < 0)
			scfdie();

		if (strcmp(dep, SCF_DEP_EXCLUDE_ALL) == 0)
			continue;

		if (scf_pg_get_property(g_pg, SCF_PROPERTY_ENTITIES, g_prop) !=
		    SCF_SUCCESS) {
			if (scf_error() != SCF_ERROR_NOT_FOUND)
				scfdie();

			continue;
		}

		if (scf_iter_property_values(viter, g_prop) != SCF_SUCCESS)
			scfdie();

		while ((vret = scf_iter_next_value(viter, g_val)) == 1) {
			if (scf_value_get_astring(g_val, dep,
			    max_scf_value_length + 1) < 0)
				scfdie();

			wip->fmri = dep;
			if (callback(data, wip) != 0)
				goto out;
		}
		if (vret == -1)
			scfdie();
	}
	if (ret == -1)
		scfdie();

out:
	scf_iter_destroy(viter);
	scf_iter_destroy(iter);
	scf_snapshot_destroy(snap);
}

static int
list_dependencies(void *data, scf_walkinfo_t *wip)
{
	walk_dependencies(wip, list_svc_or_inst_fmri, data);
	return (0);
}


/*
 * Dependent selection: The "providing" service's or instance's FMRI is parsed
 * into the provider_* variables, the instances are walked, and any instance
 * which lists an FMRI which parses to these components is selected.  This is
 * inefficient in the face of multiple operands, but that should be uncommon.
 */

static char *provider_scope;
static char *provider_svc;
static char *provider_inst;	/* NULL for services */

/*ARGSUSED*/
static int
check_against_provider(void *arg, scf_walkinfo_t *wip)
{
	char *cfmri;
	const char *scope_name, *svc_name, *inst_name, *pg_name;
	int *matchp = arg;

	cfmri = safe_strdup(wip->fmri);

	if (scf_parse_svc_fmri(cfmri, &scope_name, &svc_name, &inst_name,
	    &pg_name, NULL) != SCF_SUCCESS) {
		free(cfmri);
		return (0);
	}

	if (svc_name == NULL || pg_name != NULL) {
		free(cfmri);
		return (0);
	}

	/*
	 * If the user has specified an instance, then also match dependencies
	 * on the service itself.
	 */
	*matchp = (strcmp(provider_scope, scope_name) == 0 &&
	    strcmp(provider_svc, svc_name) == 0 &&
	    (provider_inst == NULL ? (inst_name == NULL) :
	    (inst_name == NULL || strcmp(provider_inst, inst_name) == 0)));

	free(cfmri);

	/* Stop on matches. */
	return (*matchp);
}

static int
list_if_dependent(void *unused, scf_walkinfo_t *wip)
{
	/* Only proceed if this instance depends on provider_*. */
	int match = 0;

	(void) walk_dependencies(wip, check_against_provider, &match);

	if (match)
		return (list_instance(unused, wip));

	return (0);
}

/*ARGSUSED*/
static int
list_dependents(void *unused, scf_walkinfo_t *wip)
{
	char *save;
	int ret;

	if (scf_scope_get_name(wip->scope, provider_scope,
	    max_scf_fmri_length) <= 0 ||
	    scf_service_get_name(wip->svc, provider_svc,
	    max_scf_fmri_length) <= 0)
		scfdie();

	save = provider_inst;
	if (wip->inst == NULL)
		provider_inst = NULL;
	else if (scf_instance_get_name(wip->inst, provider_inst,
	    max_scf_fmri_length) <= 0)
		scfdie();

	ret = scf_walk_fmri(h, 0, NULL, 0, list_if_dependent, NULL, NULL,
	    uu_warn);

	provider_inst = save;

	return (ret);
}

/*
 * main() & helpers
 */

static void
add_sort_column(const char *col, int reverse)
{
	int i;

	++opt_snum;

	opt_sort = realloc(opt_sort, opt_snum * sizeof (*opt_sort));
	if (opt_sort == NULL)
		uu_die(gettext("Too many sort criteria: out of memory.\n"));

	for (i = 0; i < ncolumns; ++i) {
		if (strcasecmp(col, columns[i].name) == 0)
			break;
	}

	if (i < ncolumns)
		opt_sort[opt_snum - 1] = (reverse ? i | 0x100 : i);
	else
		uu_die(gettext("Unrecognized sort column \"%s\".\n"), col);

	sortkey_sz += columns[i].sortkey_width;
}

static void
add_restarter(const char *fmri)
{
	char *cfmri;
	const char *pg_name;
	struct pfmri_list *rest;

	cfmri = safe_strdup(fmri);
	rest = safe_malloc(sizeof (*rest));

	if (scf_parse_svc_fmri(cfmri, &rest->scope, &rest->service,
	    &rest->instance, &pg_name, NULL) != SCF_SUCCESS)
		uu_die(gettext("Restarter FMRI \"%s\" is invalid.\n"), fmri);

	if (rest->instance == NULL || pg_name != NULL)
		uu_die(gettext("Restarter FMRI \"%s\" does not designate an "
		    "instance.\n"), fmri);

	rest->next = restarters;
	restarters = rest;
	return;

err:
	free(cfmri);
	free(rest);
}

/* ARGSUSED */
static int
line_cmp(const void *l_arg, const void *r_arg, void *private)
{
	const struct avl_string *l = l_arg;
	const struct avl_string *r = r_arg;

	return (memcmp(l->key, r->key, sortkey_sz));
}

/* ARGSUSED */
static int
print_line(void *e, void *private)
{
	struct avl_string *lp = e;

	(void) puts(lp->str);

	return (UU_WALK_NEXT);
}

/* ARGSUSED */
static void
errignore(const char *str, ...)
{}

int
main(int argc, char **argv)
{
	char opt, opt_mode;
	int i, n;
	char *columns_str = NULL;
	char *cp;
	const char *progname;
	int err, missing = 1, ignored, *errarg;
	uint_t nzents = 0, zent = 0;
	zoneid_t *zids = NULL;
	char zonename[ZONENAME_MAX];
	void (*errfunc)(const char *, ...);

	int show_all = 0;
	int show_header = 1;
	int show_zones = 0;

	const char * const options = "aHpvno:R:s:S:dDlL?xZz:";

	(void) setlocale(LC_ALL, "");

	locale = setlocale(LC_MESSAGES, NULL);
	if (locale) {
		locale = safe_strdup(locale);
		_scf_sanitize_locale(locale);
	}

	(void) textdomain(TEXT_DOMAIN);
	progname = uu_setpname(argv[0]);

	exit_status = UU_EXIT_OK;

	max_scf_name_length = scf_limit(SCF_LIMIT_MAX_NAME_LENGTH);
	max_scf_value_length = scf_limit(SCF_LIMIT_MAX_VALUE_LENGTH);
	max_scf_fmri_length = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH);
	max_scf_type_length = scf_limit(SCF_LIMIT_MAX_PG_TYPE_LENGTH);

	if (max_scf_name_length == -1 || max_scf_value_length == -1 ||
	    max_scf_fmri_length == -1 || max_scf_type_length == -1)
		scfdie();

	now = time(NULL);
	assert(now != -1);

	/*
	 * opt_mode is the mode of operation.  0 for plain, 'd' for
	 * dependencies, 'D' for dependents, and 'l' for detailed (long).  We
	 * need to know now so we know which options are valid.
	 */
	opt_mode = 0;
	while ((opt = getopt(argc, argv, options)) != -1) {
		switch (opt) {
		case '?':
			if (optopt == '?') {
				print_help(progname);
				return (UU_EXIT_OK);
			} else {
				argserr(progname);
				/* NOTREACHED */
			}

		case 'd':
		case 'D':
		case 'l':
		case 'L':
			if (opt_mode != 0)
				argserr(progname);

			opt_mode = opt;
			break;

		case 'n':
			if (opt_mode != 0)
				argserr(progname);

			opt_mode = opt;
			break;

		case 'x':
			if (opt_mode != 0)
				argserr(progname);

			opt_mode = opt;
			break;

		default:
			break;
		}
	}

	sortkey_sz = 0;

	optind = 1;	/* Reset getopt() */
	while ((opt = getopt(argc, argv, options)) != -1) {
		switch (opt) {
		case 'a':
			if (opt_mode != 0)
				argserr(progname);
			show_all = 1;
			break;

		case 'H':
			if (opt_mode == 'l' || opt_mode == 'x')
				argserr(progname);
			show_header = 0;
			break;

		case 'p':
			if (opt_mode == 'x')
				argserr(progname);
			opt_processes = 1;
			break;

		case 'v':
			opt_verbose = 1;
			break;

		case 'o':
			if (opt_mode == 'l' || opt_mode == 'x')
				argserr(progname);
			columns_str = optarg;
			break;

		case 'R':
			if (opt_mode != 0 || opt_mode == 'x')
				argserr(progname);

			add_restarter(optarg);
			break;

		case 's':
		case 'S':
			if (opt_mode != 0)
				argserr(progname);

			add_sort_column(optarg, optopt == 'S');
			break;

		case 'd':
		case 'D':
		case 'l':
		case 'L':
		case 'n':
		case 'x':
			assert(opt_mode == optopt);
			break;

		case 'z':
			if (getzoneid() != GLOBAL_ZONEID)
				uu_die(gettext("svcs -z may only be used from "
				    "the global zone\n"));
			if (show_zones)
				argserr(progname);

			opt_zone = optarg;
			break;

		case 'Z':
			if (getzoneid() != GLOBAL_ZONEID)
				uu_die(gettext("svcs -Z may only be used from "
				    "the global zone\n"));
			if (opt_zone != NULL)
				argserr(progname);

			show_zones = 1;
			break;

		case '?':
			argserr(progname);

		default:
			assert(0);
			abort();
		}
	}

	/*
	 * -a is only meaningful when given no arguments
	 */
	if (show_all && optind != argc)
		uu_warn(gettext("-a ignored when used with arguments.\n"));

	while (show_zones) {
		uint_t found;

		if (zone_list(NULL, &nzents) != 0)
			uu_die(gettext("could not get number of zones"));

		if ((zids = malloc(nzents * sizeof (zoneid_t))) == NULL) {
			uu_die(gettext("could not allocate array for "
			    "%d zone IDs"), nzents);
		}

		found = nzents;

		if (zone_list(zids, &found) != 0)
			uu_die(gettext("could not get zone list"));

		/*
		 * If the number of zones has not changed between our calls to
		 * zone_list(), we're done -- otherwise, we must free our array
		 * of zone IDs and take another lap.
		 */
		if (found == nzents)
			break;

		free(zids);
	}

	argc -= optind;
	argv += optind;

again:
	h = scf_handle_create(SCF_VERSION);
	if (h == NULL)
		scfdie();

	if (opt_zone != NULL || zids != NULL) {
		scf_value_t *zone;

		assert(opt_zone == NULL || zids == NULL);

		if (opt_zone == NULL) {
			zone_status_t status;

			if (zone_getattr(zids[zent], ZONE_ATTR_STATUS,
			    &status, sizeof (status)) < 0 ||
			    status != ZONE_IS_RUNNING) {
				/*
				 * If this zone is not running or we cannot
				 * get its status, we do not want to attempt
				 * to bind an SCF handle to it, lest we
				 * accidentally interfere with a zone that
				 * is not yet running by looking up a door
				 * to its svc.configd (which could potentially
				 * block a mount with an EBUSY).
				 */
				zent++;
				goto nextzone;
			}

			if (getzonenamebyid(zids[zent++],
			    zonename, sizeof (zonename)) < 0) {
				uu_warn(gettext("could not get name for "
				    "zone %d; ignoring"), zids[zent - 1]);
				goto nextzone;
			}

			g_zonename = zonename;
		} else {
			g_zonename = opt_zone;
		}

		if ((zone = scf_value_create(h)) == NULL)
			scfdie();

		if (scf_value_set_astring(zone, g_zonename) != SCF_SUCCESS)
			scfdie();

		if (scf_handle_decorate(h, "zone", zone) != SCF_SUCCESS)
			uu_die(gettext("invalid zone '%s'\n"), g_zonename);

		scf_value_destroy(zone);
	}

	if (scf_handle_bind(h) == -1) {
		if (g_zonename != NULL) {
			if (show_zones)
				goto nextzone;

			uu_die(gettext("Could not bind to repository "
			    "server for zone %s: %s\n"), g_zonename,
			    scf_strerror(scf_error()));
		}

		uu_die(gettext("Could not bind to repository server: %s.  "
		    "Exiting.\n"), scf_strerror(scf_error()));
	}

	if ((g_pg = scf_pg_create(h)) == NULL ||
	    (g_prop = scf_property_create(h)) == NULL ||
	    (g_val = scf_value_create(h)) == NULL)
		scfdie();

	if (show_zones) {
		/*
		 * It's hard to avoid editorializing here, but suffice it to
		 * say that scf_walk_fmri() takes an error handler, the
		 * interface to which has been regrettably misdesigned:  the
		 * handler itself takes exclusively a string -- even though
		 * scf_walk_fmri() has detailed, programmatic knowledge
		 * of the error condition at the time it calls its errfunc.
		 * That is, only the error message and not the error semantics
		 * are given to the handler.  This is poor interface at best,
		 * but it is particularly problematic when we are talking to
		 * multiple repository servers (as when we are iterating over
		 * all zones) as we do not want to treat failure to find a
		 * match in one zone as overall failure.  Ideally, we would
		 * simply ignore SCF_MSG_PATTERN_NOINSTANCE and correctly
		 * process the others, but alas, no such interface exists --
		 * and we must settle for instead ignoring all errfunc-called
		 * errors in the case that we are iterating over all zones...
		 */
		errfunc = errignore;
		errarg = missing ? &missing : &ignored;
		missing = 0;
	} else {
		errfunc = uu_warn;
		errarg = &exit_status;
	}

	/*
	 * If we're in long mode, take care of it now before we deal with the
	 * sorting and the columns, since we won't use them anyway.
	 */
	if (opt_mode == 'l') {
		if (argc == 0)
			argserr(progname);

		if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE,
		    print_detailed, NULL, errarg, errfunc)) != 0) {
			uu_warn(gettext("failed to iterate over "
			    "instances: %s\n"), scf_strerror(err));
			exit_status = UU_EXIT_FATAL;
		}

		goto nextzone;
	}

	if (opt_mode == 'L') {
		if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE,
		    print_log, NULL, errarg, errfunc)) != 0) {
			uu_warn(gettext("failed to iterate over "
			    "instances: %s\n"), scf_strerror(err));
			exit_status = UU_EXIT_FATAL;
		}

		goto nextzone;
	}

	if (opt_mode == 'n') {
		print_notify_special();
		if ((err = scf_walk_fmri(h, argc, argv, SCF_WALK_MULTIPLE,
		    print_notify, NULL, errarg, errfunc)) != 0) {
			uu_warn(gettext("failed to iterate over "
			    "instances: %s\n"), scf_strerror(err));
			exit_status = UU_EXIT_FATAL;
		}

		goto nextzone;
	}

	if (opt_mode == 'x') {
		explain(opt_verbose, argc, argv);
		goto nextzone;
	}

	if (columns_str == NULL) {
		if (opt_snum == 0) {
			if (show_zones)
				add_sort_column("zone", 0);

			/* Default sort. */
			add_sort_column("state", 0);
			add_sort_column("stime", 0);
			add_sort_column("fmri", 0);
		}

		if (!opt_verbose) {
			columns_str = safe_strdup(show_zones ?
			    "zone,state,stime,fmri" : "state,stime,fmri");
		} else {
			columns_str = safe_strdup(show_zones ?
			    "zone,state,nstate,stime,ctid,fmri" :
			    "state,nstate,stime,ctid,fmri");
		}
	}

	if (opt_columns == NULL) {
		/* Decode columns_str into opt_columns. */
		line_sz = 0;

		opt_cnum = 1;
		for (cp = columns_str; *cp != '\0'; ++cp)
			if (*cp == ',')
				++opt_cnum;

		if (*columns_str == '\0')
			uu_die(gettext("No columns specified.\n"));

		opt_columns = malloc(opt_cnum * sizeof (*opt_columns));
		if (opt_columns == NULL)
			uu_die(gettext("Too many columns.\n"));

		for (n = 0; *columns_str != '\0'; ++n) {
			i = getcolumnopt(&columns_str);
			if (i == -1)
				uu_die(gettext("Unknown column \"%s\".\n"),
				    columns_str);

			if (strcmp(columns[i].name, "N") == 0 ||
			    strcmp(columns[i].name, "SN") == 0 ||
			    strcmp(columns[i].name, "NSTA") == 0 ||
			    strcmp(columns[i].name, "NSTATE") == 0)
				opt_nstate_shown = 1;

			opt_columns[n] = i;
			line_sz += columns[i].width + 1;
		}

		if ((lines_pool = uu_avl_pool_create("lines_pool",
		    sizeof (struct avl_string), offsetof(struct avl_string,
		    node), line_cmp, UU_AVL_DEBUG)) == NULL ||
		    (lines = uu_avl_create(lines_pool, NULL, 0)) == NULL)
			uu_die(gettext("Unexpected libuutil error: %s\n"),
			    uu_strerror(uu_error()));
	}

	switch (opt_mode) {
	case 0:
		/*
		 * If we already have a hash table (e.g., because we are
		 * processing multiple zones), destroy it before creating
		 * a new one.
		 */
		if (ht_buckets != NULL)
			ht_free();

		ht_init();

		/* Always show all FMRIs when given arguments or restarters */
		if (argc != 0 || restarters != NULL)
			show_all =  1;

		if ((err = scf_walk_fmri(h, argc, argv,
		    SCF_WALK_MULTIPLE | SCF_WALK_LEGACY,
		    show_all ? list_instance : list_if_enabled, NULL,
		    errarg, errfunc)) != 0) {
			uu_warn(gettext("failed to iterate over "
			    "instances: %s\n"), scf_strerror(err));
			exit_status = UU_EXIT_FATAL;
		}
		break;

	case 'd':
		if (argc == 0)
			argserr(progname);

		if ((err = scf_walk_fmri(h, argc, argv,
		    SCF_WALK_MULTIPLE, list_dependencies, NULL,
		    errarg, errfunc)) != 0) {
			uu_warn(gettext("failed to iterate over "
			    "instances: %s\n"), scf_strerror(err));
			exit_status = UU_EXIT_FATAL;
		}
		break;

	case 'D':
		if (argc == 0)
			argserr(progname);

		provider_scope = safe_malloc(max_scf_fmri_length);
		provider_svc = safe_malloc(max_scf_fmri_length);
		provider_inst = safe_malloc(max_scf_fmri_length);

		if ((err = scf_walk_fmri(h, argc, argv,
		    SCF_WALK_MULTIPLE | SCF_WALK_SERVICE,
		    list_dependents, NULL, &exit_status, uu_warn)) != 0) {
			uu_warn(gettext("failed to iterate over "
			    "instances: %s\n"), scf_strerror(err));
			exit_status = UU_EXIT_FATAL;
		}

		free(provider_scope);
		free(provider_svc);
		free(provider_inst);
		break;

	case 'n':
		break;

	default:
		assert(0);
		abort();
	}

nextzone:
	if (show_zones && zent < nzents && exit_status == 0) {
		scf_handle_destroy(h);
		goto again;
	}

	if (show_zones && exit_status == 0)
		exit_status = missing;

	if (opt_columns == NULL)
		return (exit_status);

	if (show_header)
		print_header();

	(void) uu_avl_walk(lines, print_line, NULL, 0);

	return (exit_status);
}
